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,232 @@
1
+ """Educational Schnorr signatures and interactive proof transcripts.
2
+
3
+ This is a domain-separated teaching scheme, not Bitcoin BIP-340. Python
4
+ curve arithmetic is variable-time. Explicit nonces exist for experiments;
5
+ reusing a nonce reveals the private key.
6
+ """
7
+
8
+ import hashlib
9
+ import hmac
10
+ import secrets
11
+
12
+ from blockchainkit._validation import integer
13
+ from blockchainkit.constants import SCHNORR_DOMAIN
14
+ from blockchainkit.crypto.core.base import SchnorrSignature
15
+ from blockchainkit.crypto.systems.curves import (
16
+ SECP256K1,
17
+ Curve,
18
+ add,
19
+ encode_point,
20
+ multiply,
21
+ public_key,
22
+ )
23
+ from blockchainkit.crypto.systems.hashing import sha256
24
+
25
+
26
+ def challenge(
27
+ message: bytes,
28
+ commitment: tuple[int, int],
29
+ public: tuple[int, int],
30
+ curve: Curve = SECP256K1,
31
+ ) -> int:
32
+ """Hash the curve parameters, public key, commitment, and message to a scalar."""
33
+ if not isinstance(message, bytes):
34
+ raise TypeError("message must be bytes")
35
+ domain = f"{SCHNORR_DOMAIN}:{curve.p}:{curve.a}:{curve.b}:{curve.order}:".encode()
36
+ framed = domain + encode_point(curve.generator, curve)
37
+ framed += encode_point(commitment, curve) + encode_point(public, curve) + message
38
+ return int.from_bytes(sha256(framed), "big") % curve.order
39
+
40
+
41
+ def sign(
42
+ message: bytes,
43
+ private: int,
44
+ *,
45
+ nonce: int | None = None,
46
+ curve: Curve = SECP256K1,
47
+ ) -> SchnorrSignature:
48
+ """Sign with s = k + H(R, Q, m)*x modulo the subgroup order.
49
+
50
+ Parameters
51
+ ----------
52
+ message : bytes
53
+ Exact bytes to authenticate.
54
+ private : int
55
+ Secret scalar x.
56
+ nonce : int, optional
57
+ Experiment-only scalar k. Defaults to system randomness.
58
+ curve : Curve
59
+ Defaults to secp256k1; tiny curves illustrate algebra, not security.
60
+
61
+ Returns
62
+ -------
63
+ SchnorrSignature
64
+ Commitment R=kG and response s.
65
+
66
+ Examples
67
+ --------
68
+ >>> from blockchainkit.crypto import sign, verify, public_key
69
+ >>> signature = sign(b"lesson", 7, nonce=11)
70
+ >>> verify(b"lesson", signature, public_key(7))
71
+ True
72
+ """
73
+ public = public_key(private, curve)
74
+ if nonce is None:
75
+ nonce = secrets.randbelow(curve.order - 1) + 1
76
+ commitment = public_key(nonce, curve)
77
+ c = challenge(message, commitment, public, curve)
78
+ return SchnorrSignature(commitment, (nonce + c * private) % curve.order)
79
+
80
+
81
+ def verify_transcript(
82
+ public: tuple[int, int],
83
+ commitment: tuple[int, int],
84
+ challenge_scalar: int,
85
+ response: int,
86
+ curve: Curve = SECP256K1,
87
+ ) -> bool:
88
+ """Check the interactive Schnorr equation sG = R + cQ.
89
+
90
+ An accepting transcript is not itself proof of a live interaction:
91
+ choosing c and s first permits simulation via R=sG-cQ. Soundness needs
92
+ an unpredictable verifier challenge after the prover commits to R.
93
+ """
94
+ for scalar in (challenge_scalar, response):
95
+ if type(scalar) is not int or not 0 <= scalar < curve.order:
96
+ return False
97
+ for point in (public, commitment):
98
+ if point is None or not curve.contains(point):
99
+ return False
100
+ # With cofactor 1 every curve point is in the subgroup (see Curve.cofactor_is_one).
101
+ if not curve.cofactor_is_one and multiply(curve.order, point, curve) is not None:
102
+ return False
103
+ return multiply(response, curve.generator, curve) == add(
104
+ commitment, multiply(challenge_scalar, public, curve), curve
105
+ )
106
+
107
+
108
+ def simulate_transcript(
109
+ public: tuple[int, int],
110
+ challenge_scalar: int,
111
+ response: int,
112
+ curve: Curve = SECP256K1,
113
+ ) -> tuple[int, int]:
114
+ """Forge an accepting transcript *without* the private key: R = sG - cQ.
115
+
116
+ Choosing the challenge and response first, then solving for the
117
+ commitment, produces ``(R, c, s)`` that :func:`verify_transcript` accepts.
118
+ This is the simulator in the zero-knowledge proof (Goldwasser, Micali and
119
+ Rackoff, 1985): real transcripts and simulated ones look the same, so a
120
+ transcript teaches the verifier nothing. A live proof is sound only
121
+ because the verifier picks c *after* seeing R.
122
+ """
123
+ for scalar in (challenge_scalar, response):
124
+ integer(scalar, "scalar")
125
+ if scalar >= curve.order:
126
+ raise ValueError("scalars must be below the curve order")
127
+ negated = multiply(-challenge_scalar, public, curve)
128
+ commitment = add(multiply(response, curve.generator, curve), negated, curve)
129
+ if commitment is None:
130
+ raise ValueError("these scalars give the point at infinity; choose others")
131
+ return commitment
132
+
133
+
134
+ def deterministic_nonce(private: int, message: bytes, order: int = SECP256K1.order) -> int:
135
+ """Derive a signing nonce from the key and message (RFC 6979, HMAC-SHA256).
136
+
137
+ Random nonces fail when the randomness does: a repeated or predictable
138
+ nonce reveals the private key (see :func:`recover_reused_nonce_key`).
139
+ RFC 6979 removes the random number generator from signing: the nonce is
140
+ an HMAC-DRBG output seeded with the private key and the message hash, so
141
+ it is unpredictable without the key, and distinct messages get distinct
142
+ nonces. This implements section 3.2 with SHA-256, and reproduces the
143
+ RFC's published nonces when given the same order.
144
+
145
+ Parameters
146
+ ----------
147
+ private : int
148
+ Secret scalar in [1, order).
149
+ message : bytes
150
+ The message to be signed (it is hashed here).
151
+ order : int
152
+ The group order q; defaults to secp256k1's.
153
+
154
+ Examples
155
+ --------
156
+ >>> from blockchainkit.crypto import deterministic_nonce, sign, verify, public_key
157
+ >>> k = deterministic_nonce(7, b"lesson")
158
+ >>> verify(b"lesson", sign(b"lesson", 7, nonce=k), public_key(7))
159
+ True
160
+ """
161
+ integer(order, "order", 2)
162
+ integer(private, "private", 1)
163
+ if private >= order:
164
+ raise ValueError("private key must be below the order")
165
+ if not isinstance(message, bytes):
166
+ raise TypeError("message must be bytes")
167
+ qlen = order.bit_length()
168
+ rlen = (qlen + 7) // 8
169
+
170
+ def bits2int(data: bytes) -> int:
171
+ value = int.from_bytes(data, "big")
172
+ excess = 8 * len(data) - qlen
173
+ return value >> excess if excess > 0 else value
174
+
175
+ def mac(key: bytes, data: bytes) -> bytes:
176
+ return hmac.new(key, data, hashlib.sha256).digest()
177
+
178
+ seed = private.to_bytes(rlen, "big") + (bits2int(sha256(message)) % order).to_bytes(rlen, "big")
179
+ v, k = b"\x01" * 32, b"\x00" * 32
180
+ k = mac(k, v + b"\x00" + seed)
181
+ v = mac(k, v)
182
+ k = mac(k, v + b"\x01" + seed)
183
+ v = mac(k, v)
184
+ while True:
185
+ t = b""
186
+ while 8 * len(t) < qlen:
187
+ v = mac(k, v)
188
+ t += v
189
+ nonce = bits2int(t)
190
+ if 1 <= nonce < order:
191
+ return nonce
192
+ k = mac(k, v + b"\x00")
193
+ v = mac(k, v)
194
+
195
+
196
+ def verify(
197
+ message: bytes,
198
+ signature: SchnorrSignature,
199
+ public: tuple[int, int],
200
+ curve: Curve = SECP256K1,
201
+ ) -> bool:
202
+ """Verify a Schnorr signature; malformed points or scalars return False."""
203
+ try:
204
+ c = challenge(message, signature.commitment, public, curve)
205
+ return verify_transcript(public, signature.commitment, c, signature.response, curve)
206
+ except (ValueError, TypeError, AttributeError, OverflowError):
207
+ return False
208
+
209
+
210
+ def recover_reused_nonce_key(
211
+ first: SchnorrSignature,
212
+ second: SchnorrSignature,
213
+ first_challenge: int,
214
+ second_challenge: int,
215
+ order: int = SECP256K1.order,
216
+ ) -> int:
217
+ """Recover x=(s1-s2)/(c1-c2) when two signatures reuse their nonce.
218
+
219
+ This demonstrates special soundness and why nonce reuse is catastrophic.
220
+ The caller supplies the actual challenges derived from the two messages.
221
+ """
222
+ integer(order, "order", 2)
223
+ if first.commitment != second.commitment:
224
+ raise ValueError("commitments must be identical")
225
+ for scalar in (first.response, second.response, first_challenge, second_challenge):
226
+ integer(scalar, "scalar")
227
+ if scalar >= order:
228
+ raise ValueError("scalar must be below order")
229
+ delta = (first_challenge - second_challenge) % order
230
+ if delta == 0:
231
+ raise ValueError("challenges must differ modulo order")
232
+ return (first.response - second.response) * pow(delta, -1, order) % order
@@ -0,0 +1,5 @@
1
+ """Number-theoretic helpers that support the cryptographic systems."""
2
+
3
+ from blockchainkit.crypto.utils.primes import is_prime
4
+
5
+ __all__ = ["is_prime"]
@@ -0,0 +1,39 @@
1
+ """Deterministic primality testing for the bounded parameters used in experiments."""
2
+
3
+
4
+ def is_prime(value: int) -> bool:
5
+ """Test primality deterministically for integers below 2**64.
6
+
7
+ Uses trial division followed by deterministic Miller--Rabin bases for
8
+ this bounded domain. Larger numbers are deliberately not accepted.
9
+
10
+ >>> from blockchainkit.crypto.utils import is_prime
11
+ >>> is_prime(7919), is_prime(561)
12
+ (True, False)
13
+ """
14
+ if type(value) is not int:
15
+ raise TypeError(f"expected an integer, not {type(value).__name__}")
16
+ if value >= 2**64:
17
+ raise ValueError("expected an integer below 2**64")
18
+ if value < 2:
19
+ return False
20
+ for p in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37):
21
+ if value % p == 0:
22
+ return value == p
23
+ d, s = value - 1, 0
24
+ while d % 2 == 0:
25
+ d //= 2
26
+ s += 1
27
+ for base in (2, 325, 9375, 28178, 450775, 9780504, 1795265022):
28
+ if base % value == 0:
29
+ continue
30
+ x = pow(base, d, value)
31
+ if x in (1, value - 1):
32
+ continue
33
+ for _ in range(s - 1):
34
+ x = pow(x, 2, value)
35
+ if x == value - 1:
36
+ break
37
+ else:
38
+ return False
39
+ return True
@@ -0,0 +1,9 @@
1
+ """Plotting helpers for blockchainkit.crypto.
2
+
3
+ Imports matplotlib, so ``import blockchainkit`` does not load this module;
4
+ import it explicitly: ``from blockchainkit.crypto.visualizers import ...``.
5
+ """
6
+
7
+ from blockchainkit.crypto.visualizers.plots import plot_curve_points, plot_hamming_distances
8
+
9
+ __all__ = ["plot_curve_points", "plot_hamming_distances"]
@@ -0,0 +1,88 @@
1
+ """Plotting helpers for blockchainkit.crypto: small curve groups and hash avalanche."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from math import comb
7
+
8
+ import matplotlib.pyplot as plt
9
+ import numpy as np
10
+ from matplotlib.axes import Axes
11
+
12
+ from blockchainkit.crypto.systems.curves import Curve, enumerate_points, multiply
13
+
14
+ __all__ = ["plot_curve_points", "plot_hamming_distances"]
15
+
16
+
17
+ def plot_curve_points(
18
+ curve: Curve, *, label_multiples: bool = True, ax: Axes | None = None
19
+ ) -> Axes:
20
+ """Scatter every finite point of a small curve and label the multiples kG.
21
+
22
+ Parameters
23
+ ----------
24
+ curve : Curve
25
+ A curve with p <= 10,000
26
+ (see :func:`~blockchainkit.crypto.systems.curves.enumerate_points`).
27
+ label_multiples : bool
28
+ Annotate each point kG of the generator's subgroup with its k, which
29
+ shows that scalar multiplication jumps around the plane with no
30
+ visible pattern: the discrete-log problem.
31
+ ax : matplotlib.axes.Axes, optional
32
+ Axes to draw on; a new figure is created if omitted.
33
+
34
+ Returns
35
+ -------
36
+ matplotlib.axes.Axes
37
+ """
38
+ if ax is None:
39
+ _, ax = plt.subplots()
40
+ points = np.array(enumerate_points(curve))
41
+ ax.scatter(points[:, 0], points[:, 1], s=40, color="#94a3b8", label="curve points")
42
+ if label_multiples:
43
+ for k in range(1, curve.order):
44
+ point = multiply(k, curve.generator, curve)
45
+ assert point is not None # k < order, so kG is finite.
46
+ ax.scatter(*point, s=55, color="#2563eb")
47
+ ax.annotate(f"{k}G", point, xytext=(5, 5), textcoords="offset points", fontsize=8)
48
+ ax.set_xlim(-1, curve.p)
49
+ ax.set_ylim(-1, curve.p)
50
+ ax.set_xlabel("x")
51
+ ax.set_ylabel("y")
52
+ ax.set_title(f"y² = x³ + {curve.a}x + {curve.b} over F_{curve.p}: {len(points) + 1} points")
53
+ return ax
54
+
55
+
56
+ def plot_hamming_distances(
57
+ distances: Sequence[int], bits: int = 256, ax: Axes | None = None
58
+ ) -> Axes:
59
+ """Histogram of output-bit differences against the ideal Binomial(bits, 1/2).
60
+
61
+ Parameters
62
+ ----------
63
+ distances : sequence of int
64
+ Hamming distances between digests of related inputs, e.g. from
65
+ :func:`~blockchainkit.crypto.systems.hashing.hamming_distance` after a one-bit flip.
66
+ bits : int
67
+ Digest length in bits.
68
+ ax : matplotlib.axes.Axes, optional
69
+ Axes to draw on; a new figure is created if omitted.
70
+
71
+ Returns
72
+ -------
73
+ matplotlib.axes.Axes
74
+ """
75
+ if ax is None:
76
+ _, ax = plt.subplots()
77
+ values = np.asarray(distances)
78
+ ax.hist(values, bins=18, density=True, color="#0d9488", edgecolor="white", label="observed")
79
+ spread = 4 * np.sqrt(bits) / 2
80
+ ks = np.arange(int(bits / 2 - spread), int(bits / 2 + spread) + 1)
81
+ ideal = np.array([comb(bits, int(k)) for k in ks], dtype=float) / 2.0**bits
82
+ ax.plot(ks, ideal, color="black", label=f"Binomial({bits}, 1/2)")
83
+ ax.axvline(bits / 2, color="black", linestyle="--", linewidth=1)
84
+ ax.set_xlabel("differing output bits")
85
+ ax.set_ylabel("probability")
86
+ ax.set_title("Avalanche: about half the output bits change")
87
+ ax.legend()
88
+ return ax
@@ -0,0 +1,70 @@
1
+ """Peer-to-peer networks: topologies, logical time, dissemination, overlays, and block relay."""
2
+
3
+ from blockchainkit.network.core.base import (
4
+ BroadcastResult,
5
+ CompactBlockResult,
6
+ Delivery,
7
+ LookupResult,
8
+ Operation,
9
+ RelayCost,
10
+ RumorRun,
11
+ )
12
+ from blockchainkit.network.systems.addresses import AddressManager, eclipse_probability
13
+ from blockchainkit.network.systems.broadcast import reliable_broadcast
14
+ from blockchainkit.network.systems.clocks import (
15
+ concurrent,
16
+ happened_before,
17
+ lamport_timestamps,
18
+ vector_timestamps,
19
+ )
20
+ from blockchainkit.network.systems.epidemics import pittel_rounds, spread_rumor
21
+ from blockchainkit.network.systems.gossip import SimulatedNetwork
22
+ from blockchainkit.network.systems.kademlia import KademliaNetwork, node_id, xor_distance
23
+ from blockchainkit.network.systems.privacy import first_spy_precision
24
+ from blockchainkit.network.systems.propagation import fork_rate, simulate_fork_rate
25
+ from blockchainkit.network.systems.relay import compact_block_relay, relay_cost, short_id
26
+ from blockchainkit.network.systems.replication import ReplicatedRegister
27
+ from blockchainkit.network.systems.topology import (
28
+ Graph,
29
+ barabasi_albert,
30
+ complete_graph,
31
+ erdos_renyi,
32
+ ring_lattice,
33
+ watts_strogatz,
34
+ )
35
+
36
+ __all__ = [
37
+ "BroadcastResult",
38
+ "CompactBlockResult",
39
+ "Delivery",
40
+ "LookupResult",
41
+ "Operation",
42
+ "RelayCost",
43
+ "RumorRun",
44
+ "AddressManager",
45
+ "eclipse_probability",
46
+ "reliable_broadcast",
47
+ "concurrent",
48
+ "happened_before",
49
+ "lamport_timestamps",
50
+ "vector_timestamps",
51
+ "pittel_rounds",
52
+ "spread_rumor",
53
+ "SimulatedNetwork",
54
+ "KademliaNetwork",
55
+ "node_id",
56
+ "xor_distance",
57
+ "first_spy_precision",
58
+ "fork_rate",
59
+ "simulate_fork_rate",
60
+ "compact_block_relay",
61
+ "relay_cost",
62
+ "short_id",
63
+ "ReplicatedRegister",
64
+ "Graph",
65
+ "barabasi_albert",
66
+ "complete_graph",
67
+ "erdos_renyi",
68
+ "ring_lattice",
69
+ "watts_strogatz",
70
+ ]
@@ -0,0 +1,21 @@
1
+ """Event records and result containers."""
2
+
3
+ from blockchainkit.network.core.base import (
4
+ BroadcastResult,
5
+ CompactBlockResult,
6
+ Delivery,
7
+ LookupResult,
8
+ Operation,
9
+ RelayCost,
10
+ RumorRun,
11
+ )
12
+
13
+ __all__ = [
14
+ "BroadcastResult",
15
+ "CompactBlockResult",
16
+ "Delivery",
17
+ "LookupResult",
18
+ "Operation",
19
+ "RelayCost",
20
+ "RumorRun",
21
+ ]
@@ -0,0 +1,149 @@
1
+ """Event records and result containers for blockchainkit.network."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class Delivery:
8
+ """First receipt of a byte payload at a peer at integer simulation time."""
9
+
10
+ time: int
11
+ sender: str
12
+ recipient: str
13
+ payload: bytes
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class RumorRun:
18
+ """How a rumor spread, round by synchronous round.
19
+
20
+ Attributes
21
+ ----------
22
+ informed : tuple of int
23
+ ``informed[r]`` peers knew the rumor after round ``r``;
24
+ ``informed[0]`` is 1, the source.
25
+ n : int
26
+ Number of peers.
27
+ """
28
+
29
+ informed: tuple[int, ...]
30
+ n: int
31
+
32
+ @property
33
+ def rounds(self) -> int:
34
+ """Rounds played until the run stopped."""
35
+ return len(self.informed) - 1
36
+
37
+ @property
38
+ def complete(self) -> bool:
39
+ """True if every peer heard the rumor."""
40
+ return self.informed[-1] == self.n
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class BroadcastResult:
45
+ """Outcome of a Byzantine reliable-broadcast run.
46
+
47
+ Attributes
48
+ ----------
49
+ delivered : dict
50
+ Each correct process's delivered value, or None if it delivered nothing.
51
+ agreement : bool
52
+ No two correct processes delivered different values.
53
+ totality : bool
54
+ Either every correct process delivered or none did.
55
+ messages : int
56
+ Messages sent by correct processes.
57
+ """
58
+
59
+ delivered: dict[int, str | None]
60
+ agreement: bool
61
+ totality: bool
62
+ messages: int
63
+
64
+
65
+ @dataclass(frozen=True)
66
+ class Operation:
67
+ """One client request to a replicated register and its outcome.
68
+
69
+ Attributes
70
+ ----------
71
+ kind : str
72
+ ``"read"`` or ``"write"``.
73
+ replica : int
74
+ The replica the client contacted.
75
+ value : str or None
76
+ The value written, or the value read; None if the request failed.
77
+ ok : bool
78
+ False if the replica refused the request to stay consistent.
79
+ """
80
+
81
+ kind: str
82
+ replica: int
83
+ value: str | None
84
+ ok: bool
85
+
86
+
87
+ @dataclass(frozen=True)
88
+ class LookupResult:
89
+ """Route of a Kademlia lookup.
90
+
91
+ Attributes
92
+ ----------
93
+ path : tuple of int
94
+ Node identifiers visited, from the source to the node that answered.
95
+ """
96
+
97
+ path: tuple[int, ...]
98
+
99
+ @property
100
+ def hops(self) -> int:
101
+ """Number of messages forwarded, one fewer than the nodes on the path."""
102
+ return len(self.path) - 1
103
+
104
+ @property
105
+ def found(self) -> int:
106
+ """The node that answered, the closest to the target that the route reached."""
107
+ return self.path[-1]
108
+
109
+
110
+ @dataclass(frozen=True)
111
+ class RelayCost:
112
+ """Traffic and time to relay one block from one source to every reachable peer.
113
+
114
+ Attributes
115
+ ----------
116
+ messages : int
117
+ Messages of any kind sent.
118
+ bytes : int
119
+ Total bytes sent.
120
+ completion : int
121
+ Time until the last peer has the block, in one-way link latencies.
122
+ """
123
+
124
+ messages: int
125
+ bytes: int
126
+ completion: int
127
+
128
+
129
+ @dataclass(frozen=True)
130
+ class CompactBlockResult:
131
+ """Bytes needed to relay one block in full and as a compact block (BIP 152).
132
+
133
+ Attributes
134
+ ----------
135
+ full_bytes : int
136
+ Header plus every transaction.
137
+ compact_bytes : int
138
+ Header, nonce, short IDs, and the transactions the receiver lacked.
139
+ missing : int
140
+ Transactions the receiver had to request: absent from its mempool, or
141
+ ambiguous because their short ID collided.
142
+ round_trips : int
143
+ 1 if the mempool covered the block, 2 if a request was needed.
144
+ """
145
+
146
+ full_bytes: int
147
+ compact_bytes: int
148
+ missing: int
149
+ round_trips: int
@@ -0,0 +1,54 @@
1
+ """Concrete network models: topologies, clocks, dissemination, overlays, and relay."""
2
+
3
+ from blockchainkit.network.systems.addresses import AddressManager, eclipse_probability
4
+ from blockchainkit.network.systems.broadcast import reliable_broadcast
5
+ from blockchainkit.network.systems.clocks import (
6
+ concurrent,
7
+ happened_before,
8
+ lamport_timestamps,
9
+ vector_timestamps,
10
+ )
11
+ from blockchainkit.network.systems.epidemics import pittel_rounds, spread_rumor
12
+ from blockchainkit.network.systems.gossip import SimulatedNetwork
13
+ from blockchainkit.network.systems.kademlia import KademliaNetwork, node_id, xor_distance
14
+ from blockchainkit.network.systems.privacy import first_spy_precision
15
+ from blockchainkit.network.systems.propagation import fork_rate, simulate_fork_rate
16
+ from blockchainkit.network.systems.relay import compact_block_relay, relay_cost, short_id
17
+ from blockchainkit.network.systems.replication import ReplicatedRegister
18
+ from blockchainkit.network.systems.topology import (
19
+ Graph,
20
+ barabasi_albert,
21
+ complete_graph,
22
+ erdos_renyi,
23
+ ring_lattice,
24
+ watts_strogatz,
25
+ )
26
+
27
+ __all__ = [
28
+ "AddressManager",
29
+ "eclipse_probability",
30
+ "reliable_broadcast",
31
+ "concurrent",
32
+ "happened_before",
33
+ "lamport_timestamps",
34
+ "vector_timestamps",
35
+ "pittel_rounds",
36
+ "spread_rumor",
37
+ "SimulatedNetwork",
38
+ "KademliaNetwork",
39
+ "node_id",
40
+ "xor_distance",
41
+ "first_spy_precision",
42
+ "fork_rate",
43
+ "simulate_fork_rate",
44
+ "compact_block_relay",
45
+ "relay_cost",
46
+ "short_id",
47
+ "ReplicatedRegister",
48
+ "Graph",
49
+ "barabasi_albert",
50
+ "complete_graph",
51
+ "erdos_renyi",
52
+ "ring_lattice",
53
+ "watts_strogatz",
54
+ ]