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 +243 -0
- samp/encryption.py +149 -0
- samp/error.py +2 -0
- samp/extrinsic.py +154 -0
- samp/metadata.py +555 -0
- samp/py.typed +0 -0
- samp/scale.py +53 -0
- samp/secret.py +70 -0
- samp/ss58.py +85 -0
- samp/types.py +251 -0
- samp/wire.py +372 -0
- samp_core-1.1.0.dist-info/METADATA +12 -0
- samp_core-1.1.0.dist-info/RECORD +15 -0
- samp_core-1.1.0.dist-info/WHEEL +5 -0
- samp_core-1.1.0.dist-info/top_level.txt +1 -0
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
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
|
+
)
|