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,66 @@
1
+ """Consensus-relevant constants shared across blockchainkit.
2
+
3
+ Every hash domain tag, encoding bound, and default identifier lives here, so
4
+ a change to one of them is visible as a change to the protocol. Changing any
5
+ value changes hashes, signatures, or validation: update
6
+ ``docs/source/protocol.rst``, the tests, the examples, and ``CHANGELOG.md``
7
+ together.
8
+
9
+ >>> from blockchainkit import constants
10
+ >>> constants.UINT64_LIMIT == 2**64
11
+ True
12
+ """
13
+
14
+ UINT64_LIMIT = 2**64
15
+ """int: Exclusive upper bound of the unsigned 64-bit fields (nonces, amounts, heights)."""
16
+
17
+ WORD_MODULUS = 2**256
18
+ """int: The virtual machine's word size; arithmetic wraps modulo this value."""
19
+
20
+ DEFAULT_CHAIN_ID = "blockchainkit-demo"
21
+ """str: Network identifier signed into every transaction to prevent cross-chain replay."""
22
+
23
+ COMMIT_DOMAIN = b"blockchainkit:commit:v1\0"
24
+ """bytes: Domain tag prefixed to hash commitments."""
25
+
26
+ SCHNORR_DOMAIN = "blockchainkit:schnorr:v1"
27
+ """str: Domain tag prefixed, with the curve parameters, to Schnorr challenges."""
28
+
29
+ MERKLE_LEAF_PREFIX = b"\x00"
30
+ """bytes: Prefix for hashing a Merkle leaf payload."""
31
+
32
+ MERKLE_NODE_PREFIX = b"\x01"
33
+ """bytes: Prefix for hashing two child digests into a Merkle node."""
34
+
35
+ MERKLE_ROOT_PREFIX = b"\x02"
36
+ """bytes: Prefix for binding the leaf count to the top Merkle digest."""
37
+
38
+ PUZZLE_DOMAIN = b"blockchainkit:puzzle:v1"
39
+ """bytes: Domain tag for the keystream that encrypts a Merkle puzzle."""
40
+
41
+ PUZZLE_MAGIC = b"MERKLE78"
42
+ """bytes: Known plaintext prefix that tells a solver a puzzle has opened."""
43
+
44
+ LAMPORT_DOMAIN = b"blockchainkit:lamport:v1"
45
+ """bytes: Domain tag for deriving Lamport private preimages from a seed."""
46
+
47
+ PEDERSEN_DOMAIN = b"blockchainkit:pedersen:v1"
48
+ """bytes: Domain tag for deriving the second Pedersen generator."""
49
+
50
+ MUSIG_DOMAIN = b"blockchainkit:musig:v1"
51
+ """bytes: Domain tag for MuSig key-aggregation coefficients."""
52
+
53
+ MMR_ROOT_PREFIX = b"\x03"
54
+ """bytes: Prefix for bagging Merkle-mountain-range peaks into one root."""
55
+
56
+ BLOOM_DOMAIN = b"blockchainkit:bloom:v1"
57
+ """bytes: Domain tag for deriving Bloom-filter bit positions."""
58
+
59
+ UTXO_DOMAIN = "blockchainkit:utxo:v1"
60
+ """str: Domain tag signed into every UTXO transaction."""
61
+
62
+ SORTITION_DOMAIN = b"blockchainkit:sortition:v1"
63
+ """bytes: Domain tag for the hash that stands in for a VRF in cryptographic sortition."""
64
+
65
+ COMPACT_BLOCK_DOMAIN = b"blockchainkit:compact-block:v1"
66
+ """bytes: Domain tag for the salted short transaction IDs of compact blocks."""
@@ -0,0 +1,157 @@
1
+ """Cryptographic foundations: hashing, public keys, sharing, curves, and proofs."""
2
+
3
+ from blockchainkit.crypto.core.base import (
4
+ CollisionResult,
5
+ DiscreteLogResult,
6
+ FeldmanShares,
7
+ LamportKeyPair,
8
+ Point,
9
+ PuzzleSolution,
10
+ SchnorrSignature,
11
+ )
12
+ from blockchainkit.crypto.systems.asymmetric import (
13
+ TEACHING_GROUP,
14
+ DHGroup,
15
+ RSAKeyPair,
16
+ rsa_blind,
17
+ rsa_keypair,
18
+ rsa_unblind,
19
+ )
20
+ from blockchainkit.crypto.systems.commitments import (
21
+ commit,
22
+ pedersen_commit,
23
+ pedersen_generators,
24
+ verify_commitment,
25
+ )
26
+ from blockchainkit.crypto.systems.curves import (
27
+ SECP256K1,
28
+ TOY_CURVE,
29
+ Curve,
30
+ add,
31
+ encode_point,
32
+ enumerate_points,
33
+ multiply,
34
+ public_key,
35
+ )
36
+ from blockchainkit.crypto.systems.discrete_log import (
37
+ baby_step_giant_step,
38
+ pohlig_hellman,
39
+ )
40
+ from blockchainkit.crypto.systems.hashing import (
41
+ find_collision,
42
+ hamming_distance,
43
+ hash256,
44
+ sha256,
45
+ truncated_hash,
46
+ )
47
+ from blockchainkit.crypto.systems.lamport import (
48
+ lamport_keypair,
49
+ lamport_sign,
50
+ lamport_verify,
51
+ )
52
+ from blockchainkit.crypto.systems.mac import (
53
+ hmac_sha256,
54
+ naive_mac,
55
+ )
56
+ from blockchainkit.crypto.systems.merkle_damgard import (
57
+ SHA256_IV,
58
+ length_extension,
59
+ merkle_damgard_sha256,
60
+ sha256_compress,
61
+ sha256_padding,
62
+ )
63
+ from blockchainkit.crypto.systems.multisig import (
64
+ aggregate_public_keys,
65
+ musig_coefficients,
66
+ musig_sign,
67
+ )
68
+ from blockchainkit.crypto.systems.one_time_pad import (
69
+ one_time_pad,
70
+ xor_bytes,
71
+ )
72
+ from blockchainkit.crypto.systems.puzzles import (
73
+ merkle_puzzles,
74
+ solve_puzzle,
75
+ )
76
+ from blockchainkit.crypto.systems.sharing import (
77
+ feldman_split,
78
+ feldman_verify,
79
+ recover_secret,
80
+ split_secret,
81
+ )
82
+ from blockchainkit.crypto.systems.signatures import (
83
+ challenge,
84
+ deterministic_nonce,
85
+ recover_reused_nonce_key,
86
+ sign,
87
+ simulate_transcript,
88
+ verify,
89
+ verify_transcript,
90
+ )
91
+ from blockchainkit.crypto.utils.primes import (
92
+ is_prime,
93
+ )
94
+
95
+ __all__ = [
96
+ "CollisionResult",
97
+ "DiscreteLogResult",
98
+ "FeldmanShares",
99
+ "LamportKeyPair",
100
+ "Point",
101
+ "PuzzleSolution",
102
+ "SchnorrSignature",
103
+ "DHGroup",
104
+ "RSAKeyPair",
105
+ "TEACHING_GROUP",
106
+ "rsa_blind",
107
+ "rsa_keypair",
108
+ "rsa_unblind",
109
+ "commit",
110
+ "pedersen_commit",
111
+ "pedersen_generators",
112
+ "verify_commitment",
113
+ "SECP256K1",
114
+ "TOY_CURVE",
115
+ "Curve",
116
+ "add",
117
+ "encode_point",
118
+ "enumerate_points",
119
+ "multiply",
120
+ "public_key",
121
+ "baby_step_giant_step",
122
+ "pohlig_hellman",
123
+ "find_collision",
124
+ "hamming_distance",
125
+ "hash256",
126
+ "sha256",
127
+ "truncated_hash",
128
+ "lamport_keypair",
129
+ "lamport_sign",
130
+ "lamport_verify",
131
+ "hmac_sha256",
132
+ "naive_mac",
133
+ "SHA256_IV",
134
+ "length_extension",
135
+ "merkle_damgard_sha256",
136
+ "sha256_compress",
137
+ "sha256_padding",
138
+ "aggregate_public_keys",
139
+ "musig_coefficients",
140
+ "musig_sign",
141
+ "one_time_pad",
142
+ "xor_bytes",
143
+ "merkle_puzzles",
144
+ "solve_puzzle",
145
+ "feldman_split",
146
+ "feldman_verify",
147
+ "recover_secret",
148
+ "split_secret",
149
+ "challenge",
150
+ "deterministic_nonce",
151
+ "recover_reused_nonce_key",
152
+ "sign",
153
+ "simulate_transcript",
154
+ "verify",
155
+ "verify_transcript",
156
+ "is_prime",
157
+ ]
@@ -0,0 +1,21 @@
1
+ """Shared types and result containers for blockchainkit.crypto."""
2
+
3
+ from blockchainkit.crypto.core.base import (
4
+ CollisionResult,
5
+ DiscreteLogResult,
6
+ FeldmanShares,
7
+ LamportKeyPair,
8
+ Point,
9
+ PuzzleSolution,
10
+ SchnorrSignature,
11
+ )
12
+
13
+ __all__ = [
14
+ "CollisionResult",
15
+ "DiscreteLogResult",
16
+ "FeldmanShares",
17
+ "LamportKeyPair",
18
+ "Point",
19
+ "PuzzleSolution",
20
+ "SchnorrSignature",
21
+ ]
@@ -0,0 +1,109 @@
1
+ """Shared types and result containers for blockchainkit.crypto."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ Point = tuple[int, int] | None
6
+ """An affine curve point ``(x, y)``, or ``None`` for the point at infinity."""
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class SchnorrSignature:
11
+ """A finite commitment point R and response scalar s."""
12
+
13
+ commitment: tuple[int, int]
14
+ response: int
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class DiscreteLogResult:
19
+ """An exponent x with base**x = target, and the work spent finding it.
20
+
21
+ Attributes
22
+ ----------
23
+ exponent : int
24
+ The smallest non-negative solution.
25
+ group_operations : int
26
+ Group multiplications in the baby-step and giant-step tables, the
27
+ dominant cost.
28
+ """
29
+
30
+ exponent: int
31
+ group_operations: int
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class CollisionResult:
36
+ """Two distinct inputs whose truncated hashes agree.
37
+
38
+ Attributes
39
+ ----------
40
+ first, second : bytes
41
+ The colliding inputs.
42
+ digest : int
43
+ Their shared truncated hash.
44
+ trials : int
45
+ Hashes computed before the collision appeared.
46
+ """
47
+
48
+ first: bytes
49
+ second: bytes
50
+ digest: int
51
+ trials: int
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class PuzzleSolution:
56
+ """The contents of one solved Merkle puzzle, and the trials it took.
57
+
58
+ Attributes
59
+ ----------
60
+ puzzle_id : bytes
61
+ The identifier the solver announces publicly.
62
+ key : bytes
63
+ The session key, which stays secret.
64
+ trials : int
65
+ Weak keys tried before the puzzle opened.
66
+ """
67
+
68
+ puzzle_id: bytes
69
+ key: bytes
70
+ trials: int
71
+
72
+
73
+ @dataclass(frozen=True)
74
+ class LamportKeyPair:
75
+ """A Lamport one-time key: 256 pairs of secret preimages and their hashes.
76
+
77
+ Attributes
78
+ ----------
79
+ private : tuple
80
+ 256 pairs of bytes: for each digest bit, the preimage revealed when
81
+ that bit is 0 or 1.
82
+ public : tuple
83
+ 256 pairs of bytes: the SHA-256 of each private preimage.
84
+ """
85
+
86
+ private: tuple[tuple[bytes, bytes], ...]
87
+ public: tuple[tuple[bytes, bytes], ...]
88
+
89
+ def __repr__(self) -> str:
90
+ return (
91
+ f"LamportKeyPair(public[0]={self.public[0][0].hex()[:16]}..., pairs={len(self.public)})"
92
+ )
93
+
94
+
95
+ @dataclass(frozen=True)
96
+ class FeldmanShares:
97
+ """Shamir shares plus public commitments that let each holder check theirs.
98
+
99
+ Attributes
100
+ ----------
101
+ shares : tuple
102
+ ``(x, f(x))`` pairs of int, points over the integers modulo the group
103
+ order q.
104
+ commitments : tuple of int
105
+ ``g**a_j mod p`` for each polynomial coefficient ``a_j``.
106
+ """
107
+
108
+ shares: tuple[tuple[int, int], ...]
109
+ commitments: tuple[int, ...]
@@ -0,0 +1,137 @@
1
+ """Concrete cryptographic constructions, one module per family."""
2
+
3
+ from blockchainkit.crypto.systems.asymmetric import (
4
+ TEACHING_GROUP,
5
+ DHGroup,
6
+ RSAKeyPair,
7
+ rsa_blind,
8
+ rsa_keypair,
9
+ rsa_unblind,
10
+ )
11
+ from blockchainkit.crypto.systems.commitments import (
12
+ commit,
13
+ pedersen_commit,
14
+ pedersen_generators,
15
+ verify_commitment,
16
+ )
17
+ from blockchainkit.crypto.systems.curves import (
18
+ SECP256K1,
19
+ TOY_CURVE,
20
+ Curve,
21
+ add,
22
+ encode_point,
23
+ enumerate_points,
24
+ multiply,
25
+ public_key,
26
+ )
27
+ from blockchainkit.crypto.systems.discrete_log import (
28
+ baby_step_giant_step,
29
+ pohlig_hellman,
30
+ )
31
+ from blockchainkit.crypto.systems.hashing import (
32
+ find_collision,
33
+ hamming_distance,
34
+ hash256,
35
+ sha256,
36
+ truncated_hash,
37
+ )
38
+ from blockchainkit.crypto.systems.lamport import (
39
+ lamport_keypair,
40
+ lamport_sign,
41
+ lamport_verify,
42
+ )
43
+ from blockchainkit.crypto.systems.mac import (
44
+ hmac_sha256,
45
+ naive_mac,
46
+ )
47
+ from blockchainkit.crypto.systems.merkle_damgard import (
48
+ SHA256_IV,
49
+ length_extension,
50
+ merkle_damgard_sha256,
51
+ sha256_compress,
52
+ sha256_padding,
53
+ )
54
+ from blockchainkit.crypto.systems.multisig import (
55
+ aggregate_public_keys,
56
+ musig_coefficients,
57
+ musig_sign,
58
+ )
59
+ from blockchainkit.crypto.systems.one_time_pad import (
60
+ one_time_pad,
61
+ xor_bytes,
62
+ )
63
+ from blockchainkit.crypto.systems.puzzles import (
64
+ merkle_puzzles,
65
+ solve_puzzle,
66
+ )
67
+ from blockchainkit.crypto.systems.sharing import (
68
+ feldman_split,
69
+ feldman_verify,
70
+ recover_secret,
71
+ split_secret,
72
+ )
73
+ from blockchainkit.crypto.systems.signatures import (
74
+ challenge,
75
+ deterministic_nonce,
76
+ recover_reused_nonce_key,
77
+ sign,
78
+ simulate_transcript,
79
+ verify,
80
+ verify_transcript,
81
+ )
82
+
83
+ __all__ = [
84
+ "DHGroup",
85
+ "RSAKeyPair",
86
+ "TEACHING_GROUP",
87
+ "rsa_blind",
88
+ "rsa_keypair",
89
+ "rsa_unblind",
90
+ "commit",
91
+ "pedersen_commit",
92
+ "pedersen_generators",
93
+ "verify_commitment",
94
+ "SECP256K1",
95
+ "TOY_CURVE",
96
+ "Curve",
97
+ "add",
98
+ "encode_point",
99
+ "enumerate_points",
100
+ "multiply",
101
+ "public_key",
102
+ "baby_step_giant_step",
103
+ "pohlig_hellman",
104
+ "find_collision",
105
+ "hamming_distance",
106
+ "hash256",
107
+ "sha256",
108
+ "truncated_hash",
109
+ "lamport_keypair",
110
+ "lamport_sign",
111
+ "lamport_verify",
112
+ "hmac_sha256",
113
+ "naive_mac",
114
+ "SHA256_IV",
115
+ "length_extension",
116
+ "merkle_damgard_sha256",
117
+ "sha256_compress",
118
+ "sha256_padding",
119
+ "aggregate_public_keys",
120
+ "musig_coefficients",
121
+ "musig_sign",
122
+ "one_time_pad",
123
+ "xor_bytes",
124
+ "merkle_puzzles",
125
+ "solve_puzzle",
126
+ "feldman_split",
127
+ "feldman_verify",
128
+ "recover_secret",
129
+ "split_secret",
130
+ "challenge",
131
+ "deterministic_nonce",
132
+ "recover_reused_nonce_key",
133
+ "sign",
134
+ "simulate_transcript",
135
+ "verify",
136
+ "verify_transcript",
137
+ ]
@@ -0,0 +1,136 @@
1
+ """Textbook Diffie--Hellman, RSA, and RSA blinding for small experiments.
2
+
3
+ These deliberately expose the mathematics. They are not encryption APIs
4
+ for sensitive data: textbook RSA has no padding and DH has no authentication.
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from math import gcd
9
+
10
+ from blockchainkit._validation import integer
11
+ from blockchainkit.crypto.utils.primes import is_prime
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class DHGroup:
16
+ """A prime-order subgroup of the multiplicative group modulo ``p``.
17
+
18
+ Parameters
19
+ ----------
20
+ p, q : int
21
+ Small primes (below 2**64) with q dividing p-1.
22
+ g : int
23
+ Nonidentity generator of order q.
24
+
25
+ Examples
26
+ --------
27
+ >>> from blockchainkit.crypto import DHGroup
28
+ >>> group = DHGroup()
29
+ >>> group.shared(group.public(7), 3) == group.shared(group.public(3), 7)
30
+ True
31
+ """
32
+
33
+ p: int = 23
34
+ q: int = 11
35
+ g: int = 2
36
+
37
+ def __post_init__(self) -> None:
38
+ if not is_prime(self.p) or not is_prime(self.q) or (self.p - 1) % self.q:
39
+ raise ValueError("p and q must be primes with q dividing p-1")
40
+ integer(self.g, "g", 2)
41
+ if self.g >= self.p or pow(self.g, self.q, self.p) != 1:
42
+ raise ValueError("g must generate the subgroup of order q")
43
+
44
+ def public(self, private: int) -> int:
45
+ """Return g**private modulo p, for private in [1, q)."""
46
+ integer(private, "private", 1)
47
+ if private >= self.q:
48
+ raise ValueError("private must be below q")
49
+ return pow(self.g, private, self.p)
50
+
51
+ def shared(self, peer_public: int, private: int) -> int:
52
+ """Derive a shared group element after validating subgroup membership.
53
+
54
+ This is not a key derivation function and does not authenticate peers.
55
+ """
56
+ self.public(private)
57
+ integer(peer_public, "peer_public", 2)
58
+ if peer_public >= self.p or pow(peer_public, self.q, self.p) != 1:
59
+ raise ValueError("invalid peer public element")
60
+ return pow(peer_public, private, self.p)
61
+
62
+
63
+ TEACHING_GROUP = DHGroup(4611686018427377339, 2305843009213688669, 4)
64
+ """DHGroup: The order-q subgroup of a 62-bit safe prime p = 2q + 1, generated by 4.
65
+
66
+ Large enough that its discrete logs take real work by hand-written baby-step
67
+ giant-step (about 2**31 steps), small enough for exact arithmetic to stay
68
+ instant. Still far too small for real security."""
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class RSAKeyPair:
73
+ """Textbook RSA exponents: modulus n, public e, and private d.
74
+
75
+ Construct with :func:`rsa_keypair`; tiny factors make the algebra visible.
76
+ """
77
+
78
+ n: int
79
+ e: int
80
+ d: int
81
+
82
+ def encrypt(self, message: int) -> int:
83
+ """Apply the public exponent to an integer in [0, n)."""
84
+ return _rsa_apply(message, self.e, self.n)
85
+
86
+ def decrypt(self, ciphertext: int) -> int:
87
+ """Apply the private exponent; no padding or timing protections."""
88
+ return _rsa_apply(ciphertext, self.d, self.n)
89
+
90
+
91
+ def _rsa_apply(value: int, exponent: int, modulus: int) -> int:
92
+ integer(value, "value")
93
+ if value >= modulus:
94
+ raise ValueError("value must be below n")
95
+ return pow(value, exponent, modulus)
96
+
97
+
98
+ def rsa_keypair(p: int = 61, q: int = 53, e: int = 17) -> RSAKeyPair:
99
+ """Build a textbook key pair from distinct primes below 2**64.
100
+
101
+ >>> from blockchainkit.crypto import rsa_keypair
102
+ >>> key = rsa_keypair()
103
+ >>> key.encrypt(65), key.decrypt(2790)
104
+ (2790, 65)
105
+ """
106
+ if not is_prime(p) or not is_prime(q) or p == q:
107
+ raise ValueError("p and q must be distinct primes below 2**64")
108
+ phi = (p - 1) * (q - 1)
109
+ integer(e, "e", 2)
110
+ if e >= phi or gcd(e, phi) != 1:
111
+ raise ValueError("e must be below phi(n) and coprime to it")
112
+ return RSAKeyPair(p * q, e, pow(e, -1, phi))
113
+
114
+
115
+ def _check_blinding(value: int, factor: int, key: RSAKeyPair) -> None:
116
+ integer(value, "value")
117
+ if value >= key.n:
118
+ raise ValueError("value must be below n")
119
+ integer(factor, "factor", 1)
120
+ if factor >= key.n or gcd(factor, key.n) != 1:
121
+ raise ValueError("factor must be invertible modulo n and below n")
122
+
123
+
124
+ def rsa_blind(message: int, factor: int, key: RSAKeyPair) -> int:
125
+ """Return m*r**e mod n for a caller-selected invertible blinding factor.
126
+
127
+ The signer applies its private exponent to this blinded integer.
128
+ """
129
+ _check_blinding(message, factor, key)
130
+ return message * pow(factor, key.e, key.n) % key.n
131
+
132
+
133
+ def rsa_unblind(blind_signature: int, factor: int, key: RSAKeyPair) -> int:
134
+ """Remove blinding so signature**e mod n equals the original message."""
135
+ _check_blinding(blind_signature, factor, key)
136
+ return blind_signature * pow(factor, -1, key.n) % key.n