samp-core 1.1.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.
samp/__init__.py ADDED
@@ -0,0 +1,243 @@
1
+ from samp.encryption import (
2
+ ENCRYPTED_OVERHEAD,
3
+ build_capsules,
4
+ check_view_tag,
5
+ compute_view_tag,
6
+ decrypt,
7
+ decrypt_as_sender,
8
+ decrypt_from_group,
9
+ derive_group_ephemeral,
10
+ encrypt,
11
+ encrypt_for_group,
12
+ public_from_seed,
13
+ sr25519_sign,
14
+ sr25519_signing_scalar,
15
+ unseal_recipient,
16
+ )
17
+ from samp.error import SampError
18
+ from samp.extrinsic import (
19
+ ChainParams,
20
+ ExtractedCall,
21
+ ExtrinsicError,
22
+ build_signed_extrinsic,
23
+ extract_call,
24
+ extract_signer,
25
+ )
26
+ from samp.metadata import (
27
+ ErrorEntry,
28
+ ErrorTable,
29
+ FieldNotFoundError,
30
+ Metadata,
31
+ MetadataError,
32
+ ScaleError,
33
+ StorageLayout,
34
+ StorageNotFoundError,
35
+ )
36
+ from samp.scale import decode_bytes, decode_compact, encode_compact
37
+ from samp.secret import ContentKey, Seed, ViewScalar
38
+ from samp.ss58 import decode as ss58_decode
39
+ from samp.ss58 import encode as ss58_encode
40
+ from samp.types import (
41
+ CAPSULE_SIZE,
42
+ CHANNEL_DESC_MAX,
43
+ CHANNEL_NAME_MAX,
44
+ SS58_PREFIX_KUSAMA,
45
+ SS58_PREFIX_POLKADOT,
46
+ SS58_PREFIX_SUBSTRATE_GENERIC,
47
+ BlockNumber,
48
+ BlockRef,
49
+ CallArgs,
50
+ CallIdx,
51
+ Capsules,
52
+ ChannelDescription,
53
+ ChannelName,
54
+ Ciphertext,
55
+ EphPubkey,
56
+ ExtIndex,
57
+ ExtrinsicBytes,
58
+ ExtrinsicNonce,
59
+ GenesisHash,
60
+ Nonce,
61
+ PalletIdx,
62
+ Plaintext,
63
+ Pubkey,
64
+ RemarkBytes,
65
+ Signature,
66
+ SpecVersion,
67
+ Ss58Address,
68
+ Ss58Prefix,
69
+ TxVersion,
70
+ ViewTag,
71
+ block_number_from_int,
72
+ call_args_from_bytes,
73
+ call_idx_from_int,
74
+ capsules_count,
75
+ capsules_from_bytes,
76
+ ciphertext_from_bytes,
77
+ eph_pubkey_from_bytes,
78
+ ext_index_from_int,
79
+ extrinsic_bytes_from_bytes,
80
+ extrinsic_nonce_from_int,
81
+ genesis_hash_from_bytes,
82
+ nonce_from_bytes,
83
+ pallet_idx_from_int,
84
+ plaintext_from_bytes,
85
+ pubkey_from_bytes,
86
+ pubkey_zero,
87
+ remark_bytes_from_bytes,
88
+ signature_from_bytes,
89
+ spec_version_from_int,
90
+ ss58_prefix_from_int,
91
+ tx_version_from_int,
92
+ view_tag_from_int,
93
+ )
94
+ from samp.wire import (
95
+ CHANNEL_HEADER_SIZE,
96
+ SAMP_VERSION,
97
+ THREAD_HEADER_SIZE,
98
+ ApplicationRemark,
99
+ ChannelCreateRemark,
100
+ ChannelRemark,
101
+ ContentType,
102
+ EncryptedRemark,
103
+ GroupRemark,
104
+ PublicRemark,
105
+ Remark,
106
+ ThreadRemark,
107
+ content_type_from_byte,
108
+ decode_channel_content,
109
+ decode_channel_create,
110
+ decode_group_content,
111
+ decode_group_members,
112
+ decode_remark,
113
+ decode_thread_content,
114
+ encode_channel_content,
115
+ encode_channel_create,
116
+ encode_channel_msg,
117
+ encode_encrypted,
118
+ encode_group,
119
+ encode_group_members,
120
+ encode_public,
121
+ encode_thread_content,
122
+ is_samp_remark,
123
+ )
124
+
125
+ __all__ = [
126
+ "SAMP_VERSION",
127
+ "ContentType",
128
+ "content_type_from_byte",
129
+ "is_samp_remark",
130
+ "CAPSULE_SIZE",
131
+ "CHANNEL_HEADER_SIZE",
132
+ "THREAD_HEADER_SIZE",
133
+ "CHANNEL_NAME_MAX",
134
+ "CHANNEL_DESC_MAX",
135
+ "ENCRYPTED_OVERHEAD",
136
+ "Remark",
137
+ "PublicRemark",
138
+ "EncryptedRemark",
139
+ "ThreadRemark",
140
+ "ChannelCreateRemark",
141
+ "ChannelRemark",
142
+ "GroupRemark",
143
+ "ApplicationRemark",
144
+ "SampError",
145
+ "encode_public",
146
+ "encode_encrypted",
147
+ "encode_channel_msg",
148
+ "encode_channel_create",
149
+ "encode_group",
150
+ "encode_group_members",
151
+ "decode_remark",
152
+ "decode_thread_content",
153
+ "decode_channel_content",
154
+ "decode_channel_create",
155
+ "decode_group_content",
156
+ "decode_group_members",
157
+ "encode_thread_content",
158
+ "encode_channel_content",
159
+ "sr25519_sign",
160
+ "sr25519_signing_scalar",
161
+ "public_from_seed",
162
+ "encrypt",
163
+ "decrypt",
164
+ "decrypt_as_sender",
165
+ "compute_view_tag",
166
+ "check_view_tag",
167
+ "unseal_recipient",
168
+ "derive_group_ephemeral",
169
+ "build_capsules",
170
+ "encrypt_for_group",
171
+ "decrypt_from_group",
172
+ "decode_compact",
173
+ "encode_compact",
174
+ "decode_bytes",
175
+ "Metadata",
176
+ "StorageLayout",
177
+ "ErrorEntry",
178
+ "ErrorTable",
179
+ "MetadataError",
180
+ "ScaleError",
181
+ "StorageNotFoundError",
182
+ "FieldNotFoundError",
183
+ "ChainParams",
184
+ "ExtractedCall",
185
+ "ExtrinsicError",
186
+ "build_signed_extrinsic",
187
+ "extract_signer",
188
+ "extract_call",
189
+ "Seed",
190
+ "ViewScalar",
191
+ "ContentKey",
192
+ "ss58_encode",
193
+ "ss58_decode",
194
+ "BlockNumber",
195
+ "BlockRef",
196
+ "CallArgs",
197
+ "CallIdx",
198
+ "Capsules",
199
+ "ChannelDescription",
200
+ "ChannelName",
201
+ "Ciphertext",
202
+ "EphPubkey",
203
+ "ExtIndex",
204
+ "ExtrinsicBytes",
205
+ "ExtrinsicNonce",
206
+ "GenesisHash",
207
+ "Nonce",
208
+ "PalletIdx",
209
+ "Plaintext",
210
+ "Pubkey",
211
+ "RemarkBytes",
212
+ "Signature",
213
+ "SpecVersion",
214
+ "Ss58Address",
215
+ "Ss58Prefix",
216
+ "TxVersion",
217
+ "ViewTag",
218
+ "SS58_PREFIX_KUSAMA",
219
+ "SS58_PREFIX_POLKADOT",
220
+ "SS58_PREFIX_SUBSTRATE_GENERIC",
221
+ "block_number_from_int",
222
+ "call_args_from_bytes",
223
+ "call_idx_from_int",
224
+ "capsules_count",
225
+ "capsules_from_bytes",
226
+ "ciphertext_from_bytes",
227
+ "eph_pubkey_from_bytes",
228
+ "ext_index_from_int",
229
+ "extrinsic_bytes_from_bytes",
230
+ "extrinsic_nonce_from_int",
231
+ "genesis_hash_from_bytes",
232
+ "nonce_from_bytes",
233
+ "pallet_idx_from_int",
234
+ "plaintext_from_bytes",
235
+ "pubkey_from_bytes",
236
+ "pubkey_zero",
237
+ "remark_bytes_from_bytes",
238
+ "signature_from_bytes",
239
+ "spec_version_from_int",
240
+ "ss58_prefix_from_int",
241
+ "tx_version_from_int",
242
+ "view_tag_from_int",
243
+ ]
samp/encryption.py ADDED
@@ -0,0 +1,149 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ import samp_crypto
6
+
7
+ from samp.error import SampError
8
+ from samp.secret import ContentKey, Seed, ViewScalar
9
+ from samp.types import (
10
+ Capsules,
11
+ Ciphertext,
12
+ EphPubkey,
13
+ Nonce,
14
+ Plaintext,
15
+ Pubkey,
16
+ Signature,
17
+ ViewTag,
18
+ capsules_from_bytes,
19
+ ciphertext_from_bytes,
20
+ eph_pubkey_from_bytes,
21
+ plaintext_from_bytes,
22
+ pubkey_from_bytes,
23
+ signature_from_bytes,
24
+ view_tag_from_int,
25
+ )
26
+
27
+ ENCRYPTED_OVERHEAD = 80
28
+
29
+
30
+ def sr25519_sign(seed: Seed, message: bytes) -> Signature:
31
+ return signature_from_bytes(samp_crypto.sr25519_sign(seed.expose_secret(), message))
32
+
33
+
34
+ def sr25519_signing_scalar(seed: Seed) -> ViewScalar:
35
+ return ViewScalar.from_bytes(samp_crypto.sr25519_signing_scalar(seed.expose_secret()))
36
+
37
+
38
+ def public_from_seed(seed: Seed) -> Pubkey:
39
+ return pubkey_from_bytes(samp_crypto.public_from_seed(seed.expose_secret()))
40
+
41
+
42
+ def encrypt(
43
+ plaintext: Plaintext,
44
+ recipient: Pubkey,
45
+ nonce: Nonce,
46
+ sender_seed: Seed,
47
+ ) -> Ciphertext:
48
+ return ciphertext_from_bytes(
49
+ samp_crypto.encrypt_content(plaintext, recipient, nonce, sender_seed.expose_secret())
50
+ )
51
+
52
+
53
+ def decrypt(
54
+ ciphertext: Ciphertext,
55
+ nonce: Nonce,
56
+ signing_scalar: ViewScalar,
57
+ ) -> Plaintext:
58
+ return plaintext_from_bytes(
59
+ samp_crypto.decrypt_content(ciphertext, signing_scalar.expose_secret(), nonce)
60
+ )
61
+
62
+
63
+ def decrypt_as_sender(
64
+ ciphertext: Ciphertext,
65
+ nonce: Nonce,
66
+ sender_seed: Seed,
67
+ ) -> Plaintext:
68
+ return plaintext_from_bytes(
69
+ samp_crypto.decrypt_as_sender(ciphertext, sender_seed.expose_secret(), nonce)
70
+ )
71
+
72
+
73
+ def compute_view_tag(sender_seed: Seed, recipient: Pubkey, nonce: Nonce) -> ViewTag:
74
+ return view_tag_from_int(
75
+ samp_crypto.compute_view_tag(sender_seed.expose_secret(), recipient, nonce)
76
+ )
77
+
78
+
79
+ def check_view_tag(ciphertext: Ciphertext, signing_scalar: ViewScalar) -> ViewTag:
80
+ return view_tag_from_int(samp_crypto.check_view_tag(signing_scalar.expose_secret(), ciphertext))
81
+
82
+
83
+ def unseal_recipient(ciphertext: Ciphertext, nonce: Nonce, sender_seed: Seed) -> Pubkey:
84
+ return pubkey_from_bytes(
85
+ samp_crypto.unseal_recipient(ciphertext, sender_seed.expose_secret(), nonce)
86
+ )
87
+
88
+
89
+ def derive_group_ephemeral(sender_seed: Seed, nonce: Nonce) -> bytes:
90
+ result: bytes = samp_crypto.derive_group_ephemeral(sender_seed.expose_secret(), nonce)
91
+ return result
92
+
93
+
94
+ def build_capsules(
95
+ content_key: ContentKey,
96
+ member_pubkeys: list[Pubkey],
97
+ eph_scalar: bytes,
98
+ nonce: Nonce,
99
+ ) -> Capsules:
100
+ return capsules_from_bytes(
101
+ samp_crypto.build_capsules(
102
+ content_key.expose_secret(), list(member_pubkeys), eph_scalar, nonce
103
+ )
104
+ )
105
+
106
+
107
+ def scan_capsules(
108
+ data: bytes,
109
+ eph_pubkey: EphPubkey,
110
+ my_scalar: ViewScalar,
111
+ nonce: Nonce,
112
+ ) -> Optional[tuple[int, ContentKey]]:
113
+ result = samp_crypto.scan_capsules(data, eph_pubkey, my_scalar.expose_secret(), nonce)
114
+ if result is None:
115
+ return None
116
+ idx, ck = result
117
+ return int(idx), ContentKey.from_bytes(bytes(ck))
118
+
119
+
120
+ def encrypt_for_group(
121
+ plaintext: Plaintext,
122
+ member_pubkeys: list[Pubkey],
123
+ nonce: Nonce,
124
+ sender_seed: Seed,
125
+ ) -> tuple[EphPubkey, Capsules, Ciphertext]:
126
+ eph, caps, ct = samp_crypto.encrypt_for_group(
127
+ plaintext, list(member_pubkeys), nonce, sender_seed.expose_secret()
128
+ )
129
+ return (
130
+ eph_pubkey_from_bytes(eph),
131
+ capsules_from_bytes(caps),
132
+ ciphertext_from_bytes(ct),
133
+ )
134
+
135
+
136
+ def decrypt_from_group(
137
+ content: bytes,
138
+ my_scalar: ViewScalar,
139
+ nonce: Nonce,
140
+ known_n: Optional[int] = None,
141
+ ) -> Plaintext:
142
+ try:
143
+ return plaintext_from_bytes(
144
+ samp_crypto.decrypt_from_group(content, my_scalar.expose_secret(), nonce, known_n)
145
+ )
146
+ except SampError:
147
+ raise
148
+ except Exception as e:
149
+ raise SampError(f"decryption failed: {e}") from e
samp/error.py ADDED
@@ -0,0 +1,2 @@
1
+ class SampError(Exception):
2
+ pass
samp/extrinsic.py ADDED
@@ -0,0 +1,154 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ from dataclasses import dataclass
5
+ from typing import Callable, Optional
6
+
7
+ from samp.scale import decode_compact, encode_compact
8
+ from samp.types import (
9
+ CallArgs,
10
+ CallIdx,
11
+ ExtrinsicBytes,
12
+ ExtrinsicNonce,
13
+ GenesisHash,
14
+ PalletIdx,
15
+ Pubkey,
16
+ SpecVersion,
17
+ TxVersion,
18
+ call_args_from_bytes,
19
+ call_idx_from_int,
20
+ extrinsic_bytes_from_bytes,
21
+ pallet_idx_from_int,
22
+ pubkey_from_bytes,
23
+ signature_from_bytes,
24
+ )
25
+
26
+ EXT_VERSION_SIGNED = 0x84
27
+ ADDR_TYPE_ID = 0x00
28
+ SIG_TYPE_SR25519 = 0x01
29
+ ERA_IMMORTAL = 0x00
30
+ METADATA_HASH_DISABLED = 0x00
31
+ SIGNED_HEADER_LEN = 99
32
+ MIN_SIGNED_EXTRINSIC = 103
33
+ MIN_SIGNER_PAYLOAD = 34
34
+
35
+
36
+ class ExtrinsicError(Exception):
37
+ pass
38
+
39
+
40
+ @dataclass(frozen=True)
41
+ class ChainParams:
42
+ genesis_hash: GenesisHash
43
+ spec_version: SpecVersion
44
+ tx_version: TxVersion
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class ExtractedCall:
49
+ pallet: PalletIdx
50
+ call: CallIdx
51
+ args: CallArgs
52
+
53
+
54
+ def build_signed_extrinsic(
55
+ pallet_idx: PalletIdx,
56
+ call_idx: CallIdx,
57
+ call_args: CallArgs,
58
+ public_key: Pubkey,
59
+ sign: Callable[[bytes], bytes],
60
+ nonce: ExtrinsicNonce,
61
+ chain_params: ChainParams,
62
+ ) -> ExtrinsicBytes:
63
+ call_data = bytes([int(pallet_idx), int(call_idx)]) + call_args
64
+ tip = bytes([0])
65
+
66
+ signing_payload = (
67
+ call_data
68
+ + bytes([ERA_IMMORTAL])
69
+ + encode_compact(int(nonce))
70
+ + tip
71
+ + bytes([METADATA_HASH_DISABLED])
72
+ + int(chain_params.spec_version).to_bytes(4, "little")
73
+ + int(chain_params.tx_version).to_bytes(4, "little")
74
+ + chain_params.genesis_hash
75
+ + chain_params.genesis_hash
76
+ + bytes([0x00])
77
+ )
78
+
79
+ if len(signing_payload) > 256:
80
+ to_sign = hashlib.blake2b(signing_payload, digest_size=32).digest()
81
+ else:
82
+ to_sign = signing_payload
83
+
84
+ signature = signature_from_bytes(sign(to_sign))
85
+
86
+ extrinsic_payload = (
87
+ bytes([EXT_VERSION_SIGNED, ADDR_TYPE_ID])
88
+ + public_key
89
+ + bytes([SIG_TYPE_SR25519])
90
+ + signature
91
+ + bytes([ERA_IMMORTAL])
92
+ + encode_compact(int(nonce))
93
+ + tip
94
+ + bytes([METADATA_HASH_DISABLED])
95
+ + call_data
96
+ )
97
+
98
+ return extrinsic_bytes_from_bytes(encode_compact(len(extrinsic_payload)) + extrinsic_payload)
99
+
100
+
101
+ def extract_signer(extrinsic_bytes: ExtrinsicBytes) -> Optional[Pubkey]:
102
+ decoded = decode_compact(extrinsic_bytes)
103
+ if decoded is None:
104
+ return None
105
+ _, prefix_len = decoded
106
+ payload = extrinsic_bytes[prefix_len:]
107
+ if (
108
+ len(payload) < MIN_SIGNER_PAYLOAD
109
+ or payload[0] & 0x80 == 0
110
+ or payload[1] != ADDR_TYPE_ID
111
+ ):
112
+ return None
113
+ return pubkey_from_bytes(payload[2:34])
114
+
115
+
116
+ def extract_call(extrinsic_bytes: ExtrinsicBytes) -> Optional[ExtractedCall]:
117
+ decoded = decode_compact(extrinsic_bytes)
118
+ if decoded is None:
119
+ return None
120
+ _, prefix_len = decoded
121
+ payload = extrinsic_bytes[prefix_len:]
122
+
123
+ if len(payload) < MIN_SIGNED_EXTRINSIC or payload[0] & 0x80 == 0:
124
+ return None
125
+
126
+ offset = SIGNED_HEADER_LEN
127
+ if payload[offset] != 0x00:
128
+ offset += 2
129
+ else:
130
+ offset += 1
131
+
132
+ nonce = decode_compact(payload[offset:])
133
+ if nonce is None:
134
+ return None
135
+ offset += nonce[1]
136
+
137
+ tip = decode_compact(payload[offset:])
138
+ if tip is None:
139
+ return None
140
+ offset += tip[1]
141
+
142
+ offset += 1
143
+
144
+ if offset + 2 > len(payload):
145
+ return None
146
+ pallet = payload[offset]
147
+ call = payload[offset + 1]
148
+ offset += 2
149
+
150
+ return ExtractedCall(
151
+ pallet=pallet_idx_from_int(pallet),
152
+ call=call_idx_from_int(call),
153
+ args=call_args_from_bytes(payload[offset:]),
154
+ )