naeural-client 2.0.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.
- naeural_client/__init__.py +13 -0
- naeural_client/_ver.py +13 -0
- naeural_client/base/__init__.py +6 -0
- naeural_client/base/distributed_custom_code_presets.py +44 -0
- naeural_client/base/generic_session.py +1763 -0
- naeural_client/base/instance.py +616 -0
- naeural_client/base/payload/__init__.py +1 -0
- naeural_client/base/payload/payload.py +66 -0
- naeural_client/base/pipeline.py +1499 -0
- naeural_client/base/plugin_template.py +5209 -0
- naeural_client/base/responses.py +209 -0
- naeural_client/base/transaction.py +157 -0
- naeural_client/base_decentra_object.py +143 -0
- naeural_client/bc/__init__.py +3 -0
- naeural_client/bc/base.py +1046 -0
- naeural_client/bc/chain.py +0 -0
- naeural_client/bc/ec.py +324 -0
- naeural_client/certs/__init__.py +0 -0
- naeural_client/certs/r9092118.ala.eu-central-1.emqxsl.com.crt +22 -0
- naeural_client/code_cheker/__init__.py +1 -0
- naeural_client/code_cheker/base.py +520 -0
- naeural_client/code_cheker/checker.py +294 -0
- naeural_client/comm/__init__.py +2 -0
- naeural_client/comm/amqp_wrapper.py +338 -0
- naeural_client/comm/mqtt_wrapper.py +539 -0
- naeural_client/const/README.md +3 -0
- naeural_client/const/__init__.py +9 -0
- naeural_client/const/base.py +101 -0
- naeural_client/const/comms.py +80 -0
- naeural_client/const/environment.py +26 -0
- naeural_client/const/formatter.py +7 -0
- naeural_client/const/heartbeat.py +111 -0
- naeural_client/const/misc.py +20 -0
- naeural_client/const/payload.py +190 -0
- naeural_client/default/__init__.py +1 -0
- naeural_client/default/instance/__init__.py +4 -0
- naeural_client/default/instance/chain_dist_custom_job_01_plugin.py +54 -0
- naeural_client/default/instance/custom_web_app_01_plugin.py +118 -0
- naeural_client/default/instance/net_mon_01_plugin.py +45 -0
- naeural_client/default/instance/view_scene_01_plugin.py +28 -0
- naeural_client/default/session/mqtt_session.py +72 -0
- naeural_client/io_formatter/__init__.py +2 -0
- naeural_client/io_formatter/base/__init__.py +1 -0
- naeural_client/io_formatter/base/base_formatter.py +80 -0
- naeural_client/io_formatter/default/__init__.py +3 -0
- naeural_client/io_formatter/default/a_dummy.py +51 -0
- naeural_client/io_formatter/default/aixp1.py +113 -0
- naeural_client/io_formatter/default/default.py +22 -0
- naeural_client/io_formatter/io_formatter_manager.py +96 -0
- naeural_client/logging/__init__.py +1 -0
- naeural_client/logging/base_logger.py +2056 -0
- naeural_client/logging/logger_mixins/__init__.py +12 -0
- naeural_client/logging/logger_mixins/class_instance_mixin.py +92 -0
- naeural_client/logging/logger_mixins/computer_vision_mixin.py +443 -0
- naeural_client/logging/logger_mixins/datetime_mixin.py +344 -0
- naeural_client/logging/logger_mixins/download_mixin.py +421 -0
- naeural_client/logging/logger_mixins/general_serialization_mixin.py +242 -0
- naeural_client/logging/logger_mixins/json_serialization_mixin.py +481 -0
- naeural_client/logging/logger_mixins/pickle_serialization_mixin.py +301 -0
- naeural_client/logging/logger_mixins/process_mixin.py +63 -0
- naeural_client/logging/logger_mixins/resource_size_mixin.py +81 -0
- naeural_client/logging/logger_mixins/timers_mixin.py +501 -0
- naeural_client/logging/logger_mixins/upload_mixin.py +260 -0
- naeural_client/logging/logger_mixins/utils_mixin.py +675 -0
- naeural_client/logging/small_logger.py +93 -0
- naeural_client/logging/tzlocal/__init__.py +20 -0
- naeural_client/logging/tzlocal/unix.py +231 -0
- naeural_client/logging/tzlocal/utils.py +113 -0
- naeural_client/logging/tzlocal/win32.py +151 -0
- naeural_client/logging/tzlocal/windows_tz.py +718 -0
- naeural_client/plugins_manager_mixin.py +273 -0
- naeural_client/utils/__init__.py +2 -0
- naeural_client/utils/comm_utils.py +44 -0
- naeural_client/utils/dotenv.py +75 -0
- naeural_client-2.0.0.dist-info/METADATA +365 -0
- naeural_client-2.0.0.dist-info/RECORD +78 -0
- naeural_client-2.0.0.dist-info/WHEEL +4 -0
- naeural_client-2.0.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,1046 @@
|
|
1
|
+
import os
|
2
|
+
import base64
|
3
|
+
import json
|
4
|
+
import binascii
|
5
|
+
import numpy as np
|
6
|
+
import datetime
|
7
|
+
|
8
|
+
from hashlib import sha256, md5
|
9
|
+
from threading import Lock
|
10
|
+
from copy import deepcopy
|
11
|
+
|
12
|
+
|
13
|
+
from cryptography.hazmat.primitives import serialization
|
14
|
+
|
15
|
+
|
16
|
+
class BCct:
|
17
|
+
SIGN = 'EE_SIGN'
|
18
|
+
SENDER = 'EE_SENDER'
|
19
|
+
HASH = 'EE_HASH'
|
20
|
+
|
21
|
+
ADDR_PREFIX_OLD = "aixp_"
|
22
|
+
ADDR_PREFIX = "0xai_"
|
23
|
+
|
24
|
+
K_PEM_FILE = 'PEM_FILE'
|
25
|
+
K_PASSWORD = 'PASSWORD'
|
26
|
+
K_PEM_LOCATION = 'PEM_LOCATION'
|
27
|
+
|
28
|
+
ERR_UNAVL_MSG = "Missing signature/sender data"
|
29
|
+
ERR_UNAVL = 1
|
30
|
+
|
31
|
+
ERR_SIGN_MSG = "Bad hash"
|
32
|
+
ERR_UNAVL = 1000
|
33
|
+
|
34
|
+
ERR_SIGN_MSG = "Bad signature"
|
35
|
+
ERR_UNAVL = 1001
|
36
|
+
|
37
|
+
AUTHORISED_ADDRS = 'authorized_addrs'
|
38
|
+
|
39
|
+
DEFAULT_INFO = '0xai handshake data'
|
40
|
+
|
41
|
+
|
42
|
+
class _DotDict(dict):
|
43
|
+
__getattr__ = dict.__getitem__
|
44
|
+
__setattr__ = dict.__setitem__
|
45
|
+
__delattr__ = dict.__delitem__
|
46
|
+
|
47
|
+
|
48
|
+
class VerifyMessage(_DotDict):
|
49
|
+
def __init__(self):
|
50
|
+
self.valid = False
|
51
|
+
self.message = None
|
52
|
+
self.sender = None
|
53
|
+
|
54
|
+
|
55
|
+
NON_DATA_FIELDS = [BCct.HASH, BCct.SIGN, BCct.SENDER]
|
56
|
+
|
57
|
+
def replace_nan_inf(data, inplace=False):
|
58
|
+
assert isinstance(data, (dict, list)), "Only dictionaries and lists are supported"
|
59
|
+
if inplace:
|
60
|
+
d = data
|
61
|
+
else:
|
62
|
+
d = deepcopy(data)
|
63
|
+
stack = [d]
|
64
|
+
while stack:
|
65
|
+
current = stack.pop()
|
66
|
+
for key, value in current.items():
|
67
|
+
if isinstance(value, dict):
|
68
|
+
stack.append(value)
|
69
|
+
elif isinstance(value, list):
|
70
|
+
for item in value:
|
71
|
+
if isinstance(item, dict):
|
72
|
+
stack.append(item)
|
73
|
+
elif isinstance(value, float) and (np.isnan(value) or np.isinf(value)):
|
74
|
+
current[key] = None
|
75
|
+
return d
|
76
|
+
|
77
|
+
class _SimpleJsonEncoder(json.JSONEncoder):
|
78
|
+
"""
|
79
|
+
Used to help jsonify numpy arrays or lists that contain numpy data types.
|
80
|
+
"""
|
81
|
+
def default(self, obj):
|
82
|
+
if isinstance(obj, np.integer):
|
83
|
+
return int(obj)
|
84
|
+
elif isinstance(obj, np.floating):
|
85
|
+
return float(obj)
|
86
|
+
elif isinstance(obj, np.ndarray):
|
87
|
+
return obj.tolist()
|
88
|
+
elif isinstance(obj, np.ndarray):
|
89
|
+
return obj.tolist()
|
90
|
+
elif isinstance(obj, datetime.datetime):
|
91
|
+
return obj.strftime("%Y-%m-%d %H:%M:%S")
|
92
|
+
else:
|
93
|
+
return super(_SimpleJsonEncoder, self).default(obj)
|
94
|
+
|
95
|
+
class _ComplexJsonEncoder(json.JSONEncoder):
|
96
|
+
def default(self, obj):
|
97
|
+
if isinstance(obj, np.integer):
|
98
|
+
return int(obj)
|
99
|
+
elif isinstance(obj, np.floating):
|
100
|
+
return float(obj)
|
101
|
+
elif isinstance(obj, np.ndarray):
|
102
|
+
return obj.tolist()
|
103
|
+
elif isinstance(obj, datetime.datetime):
|
104
|
+
return obj.strftime("%Y-%m-%d %H:%M:%S")
|
105
|
+
else:
|
106
|
+
return super(_ComplexJsonEncoder, self).default(obj)
|
107
|
+
|
108
|
+
def iterencode(self, o, _one_shot=False):
|
109
|
+
"""Encode the given object and yield each string representation as available."""
|
110
|
+
if self.check_circular:
|
111
|
+
markers = {}
|
112
|
+
else:
|
113
|
+
markers = None
|
114
|
+
if self.ensure_ascii:
|
115
|
+
_encoder = json.encoder.encode_basestring_ascii
|
116
|
+
else:
|
117
|
+
_encoder = json.encoder.encode_basestring
|
118
|
+
|
119
|
+
def floatstr(o, allow_nan=self.allow_nan, _repr=float.__repr__, _inf=json.encoder.INFINITY, _neginf=-json.encoder.INFINITY):
|
120
|
+
if o != o: # Check for NaN
|
121
|
+
text = 'null'
|
122
|
+
elif o == _inf:
|
123
|
+
text = 'null'
|
124
|
+
elif o == _neginf:
|
125
|
+
text = 'null'
|
126
|
+
else:
|
127
|
+
return repr(o).rstrip('0').rstrip('.') if '.' in repr(o) else repr(o)
|
128
|
+
|
129
|
+
if not allow_nan:
|
130
|
+
raise ValueError("Out of range float values are not JSON compliant: " + repr(o))
|
131
|
+
|
132
|
+
return text
|
133
|
+
|
134
|
+
_iterencode = json.encoder._make_iterencode(
|
135
|
+
markers, self.default, _encoder, self.indent, floatstr,
|
136
|
+
self.key_separator, self.item_separator, self.sort_keys,
|
137
|
+
self.skipkeys, _one_shot
|
138
|
+
)
|
139
|
+
return _iterencode(o, 0)
|
140
|
+
|
141
|
+
## RIPEMD160
|
142
|
+
|
143
|
+
# Message schedule indexes for the left path.
|
144
|
+
ML = [
|
145
|
+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
146
|
+
7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8,
|
147
|
+
3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12,
|
148
|
+
1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2,
|
149
|
+
4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13
|
150
|
+
]
|
151
|
+
|
152
|
+
# Message schedule indexes for the right path.
|
153
|
+
MR = [
|
154
|
+
5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12,
|
155
|
+
6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2,
|
156
|
+
15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13,
|
157
|
+
8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14,
|
158
|
+
12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11
|
159
|
+
]
|
160
|
+
|
161
|
+
# Rotation counts for the left path.
|
162
|
+
RL = [
|
163
|
+
11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8,
|
164
|
+
7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12,
|
165
|
+
11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5,
|
166
|
+
11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12,
|
167
|
+
9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6
|
168
|
+
]
|
169
|
+
|
170
|
+
# Rotation counts for the right path.
|
171
|
+
RR = [
|
172
|
+
8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6,
|
173
|
+
9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11,
|
174
|
+
9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5,
|
175
|
+
15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8,
|
176
|
+
8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11
|
177
|
+
]
|
178
|
+
|
179
|
+
# K constants for the left path.
|
180
|
+
KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e]
|
181
|
+
|
182
|
+
# K constants for the right path.
|
183
|
+
KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0]
|
184
|
+
|
185
|
+
|
186
|
+
def fi(x, y, z, i):
|
187
|
+
"""The f1, f2, f3, f4, and f5 functions from the specification."""
|
188
|
+
if i == 0:
|
189
|
+
return x ^ y ^ z
|
190
|
+
elif i == 1:
|
191
|
+
return (x & y) | (~x & z)
|
192
|
+
elif i == 2:
|
193
|
+
return (x | ~y) ^ z
|
194
|
+
elif i == 3:
|
195
|
+
return (x & z) | (y & ~z)
|
196
|
+
elif i == 4:
|
197
|
+
return x ^ (y | ~z)
|
198
|
+
else:
|
199
|
+
assert False
|
200
|
+
|
201
|
+
|
202
|
+
def rol(x, i):
|
203
|
+
"""Rotate the bottom 32 bits of x left by i bits."""
|
204
|
+
return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff
|
205
|
+
|
206
|
+
|
207
|
+
def compress(h0, h1, h2, h3, h4, block):
|
208
|
+
"""Compress state (h0, h1, h2, h3, h4) with block."""
|
209
|
+
# Left path variables.
|
210
|
+
al, bl, cl, dl, el = h0, h1, h2, h3, h4
|
211
|
+
# Right path variables.
|
212
|
+
ar, br, cr, dr, er = h0, h1, h2, h3, h4
|
213
|
+
# Message variables.
|
214
|
+
x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)]
|
215
|
+
|
216
|
+
# Iterate over the 80 rounds of the compression.
|
217
|
+
for j in range(80):
|
218
|
+
rnd = j >> 4
|
219
|
+
# Perform left side of the transformation.
|
220
|
+
al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el
|
221
|
+
al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl
|
222
|
+
# Perform right side of the transformation.
|
223
|
+
ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er
|
224
|
+
ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr
|
225
|
+
|
226
|
+
# Compose old state, left transform, and right transform into new state.
|
227
|
+
return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr
|
228
|
+
|
229
|
+
|
230
|
+
def ripemd160(data):
|
231
|
+
"""Compute the RIPEMD-160 hash of data."""
|
232
|
+
# Initialize state.
|
233
|
+
state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0)
|
234
|
+
# Process full 64-byte blocks in the input.
|
235
|
+
for b in range(len(data) >> 6):
|
236
|
+
state = compress(*state, data[64*b:64*(b+1)])
|
237
|
+
# Construct final blocks (with padding and size).
|
238
|
+
pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63)
|
239
|
+
fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little')
|
240
|
+
# Process final blocks.
|
241
|
+
for b in range(len(fin) >> 6):
|
242
|
+
state = compress(*state, fin[64*b:64*(b+1)])
|
243
|
+
# Produce output.
|
244
|
+
return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state)
|
245
|
+
|
246
|
+
# END ## RIPEMD160
|
247
|
+
|
248
|
+
class BaseBlockEngine:
|
249
|
+
"""
|
250
|
+
This multiton (multi-singleton via key) is the base workhorse of the private blockchain.
|
251
|
+
|
252
|
+
Parameters
|
253
|
+
----------
|
254
|
+
|
255
|
+
name: str
|
256
|
+
the name of the engine. Used to create the private key file name.
|
257
|
+
|
258
|
+
config: dict
|
259
|
+
the configuration dict that contains the PEM_FILE, PASSWORD, PEM_LOCATION keys
|
260
|
+
for configuring the private key file access
|
261
|
+
|
262
|
+
log: Logger object
|
263
|
+
the Logger object
|
264
|
+
|
265
|
+
ensure_ascii_payloads: bool
|
266
|
+
flag that controls if the payloads are encoded as ascii or not. Default `False` for JS compatibility.
|
267
|
+
|
268
|
+
"""
|
269
|
+
_lock: Lock = Lock()
|
270
|
+
__instances = {}
|
271
|
+
|
272
|
+
def __new__(cls, name, config, log, ensure_ascii_payloads=False, verbosity=1):
|
273
|
+
with cls._lock:
|
274
|
+
if name not in cls.__instances:
|
275
|
+
instance = super(BaseBlockEngine, cls).__new__(cls)
|
276
|
+
instance._build(
|
277
|
+
name=name, log=log, config=config,
|
278
|
+
ensure_ascii_payloads=ensure_ascii_payloads,
|
279
|
+
verbosity=verbosity,
|
280
|
+
)
|
281
|
+
cls.__instances[name] = instance
|
282
|
+
else:
|
283
|
+
instance = cls.__instances[name]
|
284
|
+
return instance
|
285
|
+
|
286
|
+
def _build(
|
287
|
+
self,
|
288
|
+
name,
|
289
|
+
config:dict,
|
290
|
+
log=None,
|
291
|
+
ensure_ascii_payloads=False,
|
292
|
+
verbosity=1,
|
293
|
+
):
|
294
|
+
|
295
|
+
self.__name = name
|
296
|
+
assert log is not None, "Logger object was not provided!"
|
297
|
+
|
298
|
+
self.log = log
|
299
|
+
self.__private_key = None
|
300
|
+
self.__verbosity = verbosity
|
301
|
+
self.__public_key = None
|
302
|
+
self.__password = config.get(BCct.K_PASSWORD)
|
303
|
+
self.__config = config
|
304
|
+
self.__ensure_ascii_payloads = ensure_ascii_payloads
|
305
|
+
|
306
|
+
pem_name = config.get(BCct.K_PEM_FILE, '_pk.pem')
|
307
|
+
pem_folder = config.get(BCct.K_PEM_LOCATION, 'data')
|
308
|
+
pem_fn = os.path.join(log.get_target_folder(pem_folder), pem_name)
|
309
|
+
self.__pem_file = pem_fn
|
310
|
+
|
311
|
+
self._init()
|
312
|
+
return
|
313
|
+
|
314
|
+
def P(self, s, color=None, boxed=False, verbosity=1, **kwargs):
|
315
|
+
if verbosity > self.__verbosity:
|
316
|
+
return
|
317
|
+
if not boxed:
|
318
|
+
s = "<BC:{}> ".format(self.__name) + s
|
319
|
+
return self.log.P(
|
320
|
+
s,
|
321
|
+
color='g' if (color is None or color.lower() not in ['r', 'red', 'error']) else color,
|
322
|
+
boxed=boxed,
|
323
|
+
**kwargs
|
324
|
+
)
|
325
|
+
|
326
|
+
|
327
|
+
def _init(self):
|
328
|
+
self.P("Initializing Blockchain engine manager...", boxed=True, box_char='*', verbosity=1)
|
329
|
+
|
330
|
+
if True:
|
331
|
+
self.P("Initializing private blockchain:\n{}".format(
|
332
|
+
json.dumps(self.__config, indent=4)), verbosity=2
|
333
|
+
)
|
334
|
+
if self.__pem_file is not None:
|
335
|
+
try:
|
336
|
+
full_path = os.path.abspath(self.__pem_file)
|
337
|
+
self.P("Trying to load sk from {}".format(full_path), verbosity=1)
|
338
|
+
self.__private_key = self._text_to_sk(
|
339
|
+
source=self.__pem_file,
|
340
|
+
from_file=True,
|
341
|
+
password=self.__password,
|
342
|
+
)
|
343
|
+
self.P(" Loaded sk from {}".format(full_path), verbosity=1)
|
344
|
+
except:
|
345
|
+
self.P(" Failed to load sk from {}".format(full_path), color='r', verbosity=1)
|
346
|
+
|
347
|
+
if self.__private_key is None:
|
348
|
+
self.P("Creating new private key", verbosity=1)
|
349
|
+
self.__private_key = self._create_new_sk()
|
350
|
+
self._sk_to_text(
|
351
|
+
private_key=self.__private_key,
|
352
|
+
password=self.__password,
|
353
|
+
fn=self.__pem_file,
|
354
|
+
)
|
355
|
+
self.__public_key = self._get_pk(private_key=self.__private_key)
|
356
|
+
self.__address = self._pk_to_address(self.__public_key)
|
357
|
+
self.P("Current address: {}".format(self.address), boxed=True, verbosity=1)
|
358
|
+
self.P("Allowed list of senders: {}".format(self.allowed_list), verbosity=1)
|
359
|
+
return
|
360
|
+
|
361
|
+
@property
|
362
|
+
def private_key(self):
|
363
|
+
return self.__private_key
|
364
|
+
|
365
|
+
|
366
|
+
@staticmethod
|
367
|
+
def _compute_hash(data : bytes, method='SHA256'):
|
368
|
+
"""
|
369
|
+
Computes the hash of a `bytes` data message
|
370
|
+
|
371
|
+
Parameters
|
372
|
+
----------
|
373
|
+
data : bytes
|
374
|
+
the input message usually obtained from a bynary jsoned dict.
|
375
|
+
|
376
|
+
method : str, optional
|
377
|
+
the hash algo. The default is 'HASH160'.
|
378
|
+
|
379
|
+
|
380
|
+
Returns
|
381
|
+
-------
|
382
|
+
result : bytes, str
|
383
|
+
hash both in bin and text format.
|
384
|
+
|
385
|
+
"""
|
386
|
+
result = None, None
|
387
|
+
method = method.upper()
|
388
|
+
assert method in ['HASH160', 'SHA256', 'MD5']
|
389
|
+
|
390
|
+
if method == 'MD5':
|
391
|
+
hash_obj = md5(data)
|
392
|
+
result = hash_obj.digest(), hash_obj.hexdigest()
|
393
|
+
elif method == 'SHA256':
|
394
|
+
hash_obj = sha256(data)
|
395
|
+
result = hash_obj.digest(), hash_obj.hexdigest()
|
396
|
+
elif method == 'HASH160':
|
397
|
+
hb_sha256 = sha256(data).digest()
|
398
|
+
hb_h160 = ripemd160(hb_sha256)
|
399
|
+
result = hb_h160, binascii.hexlify(hb_h160).decode()
|
400
|
+
return result
|
401
|
+
|
402
|
+
|
403
|
+
@staticmethod
|
404
|
+
def _binary_to_text(data : bytes, method='base64'):
|
405
|
+
"""
|
406
|
+
Encodes a bytes message as text
|
407
|
+
|
408
|
+
Parameters
|
409
|
+
----------
|
410
|
+
data : bytes
|
411
|
+
the binary data, usually a signature, hash, etc.
|
412
|
+
|
413
|
+
method : str, optional
|
414
|
+
the method - 'base64' or other. The default is 'base64'.
|
415
|
+
|
416
|
+
|
417
|
+
Returns
|
418
|
+
-------
|
419
|
+
txt : str
|
420
|
+
the base64 or hexlified text.
|
421
|
+
|
422
|
+
"""
|
423
|
+
assert isinstance(data, bytes)
|
424
|
+
if method == 'base64':
|
425
|
+
txt = base64.urlsafe_b64encode(data).decode()
|
426
|
+
else:
|
427
|
+
txt = binascii.hexlify(data).decode()
|
428
|
+
return txt
|
429
|
+
|
430
|
+
|
431
|
+
@staticmethod
|
432
|
+
def _text_to_binary(text : str, method='base64'):
|
433
|
+
"""
|
434
|
+
Convert from str/text to binary
|
435
|
+
|
436
|
+
Parameters
|
437
|
+
----------
|
438
|
+
text : str
|
439
|
+
the message.
|
440
|
+
|
441
|
+
method : str, optional
|
442
|
+
the conversion method. The default is 'base64'.
|
443
|
+
|
444
|
+
|
445
|
+
Returns
|
446
|
+
-------
|
447
|
+
data : bytes
|
448
|
+
the decoded binary message.
|
449
|
+
|
450
|
+
"""
|
451
|
+
assert isinstance(text, str), "Cannot convert non text to binary"
|
452
|
+
if method == 'base64':
|
453
|
+
data = base64.urlsafe_b64decode(text)
|
454
|
+
else:
|
455
|
+
data = binascii.unhexlify(text)
|
456
|
+
return data
|
457
|
+
|
458
|
+
|
459
|
+
@staticmethod
|
460
|
+
def _get_pk(private_key):
|
461
|
+
"""
|
462
|
+
Simple wrapper to generate pk from sk
|
463
|
+
|
464
|
+
|
465
|
+
Returns
|
466
|
+
-------
|
467
|
+
public_key : pk
|
468
|
+
|
469
|
+
"""
|
470
|
+
return private_key.public_key()
|
471
|
+
|
472
|
+
|
473
|
+
def _get_allowed_file(self):
|
474
|
+
"""
|
475
|
+
Return the file path for the autorized addresses
|
476
|
+
"""
|
477
|
+
folder = self.log.base_folder
|
478
|
+
path = os.path.join(folder, BCct.AUTHORISED_ADDRS)
|
479
|
+
return path
|
480
|
+
|
481
|
+
def _load_and_maybe_create_allowed(self):
|
482
|
+
fn = self._get_allowed_file()
|
483
|
+
lst_allowed = []
|
484
|
+
if os.path.isfile(fn):
|
485
|
+
with open(fn, 'rt') as fh:
|
486
|
+
lst_allowed = fh.readlines()
|
487
|
+
else:
|
488
|
+
full_path = os.path.abspath(fn)
|
489
|
+
self.P("WARNING: no `{}` file found. Creating empty one.".format(full_path), verbosity=1)
|
490
|
+
with open(fn, 'wt') as fh:
|
491
|
+
fh.write('\n')
|
492
|
+
lst_allowed = [x.strip() for x in lst_allowed]
|
493
|
+
lst_allowed = [x.split()[0] for x in lst_allowed if x != '']
|
494
|
+
lst_allowed = [self._remove_prefix(x) for x in lst_allowed if x != '']
|
495
|
+
return lst_allowed
|
496
|
+
|
497
|
+
|
498
|
+
def _remove_prefix(self, address):
|
499
|
+
"""
|
500
|
+
Removes the prefix from the address
|
501
|
+
|
502
|
+
Parameters
|
503
|
+
----------
|
504
|
+
address : str
|
505
|
+
the text address.
|
506
|
+
|
507
|
+
Returns
|
508
|
+
-------
|
509
|
+
address : str
|
510
|
+
the address without the prefix.
|
511
|
+
"""
|
512
|
+
if address.startswith(BCct.ADDR_PREFIX):
|
513
|
+
address = address[len(BCct.ADDR_PREFIX):]
|
514
|
+
elif address.startswith(BCct.ADDR_PREFIX_OLD):
|
515
|
+
address = address[len(BCct.ADDR_PREFIX_OLD):]
|
516
|
+
return address
|
517
|
+
|
518
|
+
def _pk_to_address(self, public_key):
|
519
|
+
"""
|
520
|
+
Given a pk object will return the simple text address.
|
521
|
+
|
522
|
+
OBS: Should be overwritten in particular implementations using X962
|
523
|
+
|
524
|
+
|
525
|
+
Parameters
|
526
|
+
----------
|
527
|
+
public_key : pk
|
528
|
+
the pk object.
|
529
|
+
|
530
|
+
Returns
|
531
|
+
-------
|
532
|
+
text address
|
533
|
+
|
534
|
+
"""
|
535
|
+
data = public_key.public_bytes(
|
536
|
+
encoding=serialization.Encoding.DER, # will encode the full pk information
|
537
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo, # used with DER
|
538
|
+
)
|
539
|
+
txt = BCct.ADDR_PREFIX + self._binary_to_text(data)
|
540
|
+
return txt
|
541
|
+
|
542
|
+
|
543
|
+
def _address_to_pk(self, address):
|
544
|
+
"""
|
545
|
+
Given a address will return the pk object
|
546
|
+
|
547
|
+
OBS: Should be overwritten in particular implementations using X962
|
548
|
+
|
549
|
+
|
550
|
+
Parameters
|
551
|
+
----------
|
552
|
+
address : str
|
553
|
+
the text address (pk).
|
554
|
+
|
555
|
+
|
556
|
+
Returns
|
557
|
+
-------
|
558
|
+
pk : pk
|
559
|
+
the pk object.
|
560
|
+
|
561
|
+
"""
|
562
|
+
simple_address = self._remove_prefix(address)
|
563
|
+
bpublic_key = self._text_to_binary(simple_address)
|
564
|
+
# below works for DER / SubjectPublicKeyInfo
|
565
|
+
public_key = serialization.load_der_public_key(bpublic_key)
|
566
|
+
return public_key
|
567
|
+
|
568
|
+
|
569
|
+
def _text_to_sk(self, source, from_file=False, password=None):
|
570
|
+
"""
|
571
|
+
Construct a EllipticCurvePrivateKey from a text sk
|
572
|
+
|
573
|
+
Parameters
|
574
|
+
----------
|
575
|
+
source : str
|
576
|
+
the text secret key or the file name if `from_file == True`.
|
577
|
+
|
578
|
+
from_file: bool
|
579
|
+
flag that allows source to be a file
|
580
|
+
|
581
|
+
|
582
|
+
Returns
|
583
|
+
-------
|
584
|
+
sk
|
585
|
+
|
586
|
+
"""
|
587
|
+
if from_file and os.path.isfile(source):
|
588
|
+
self.P("Reading SK from '{}'".format(source), color='g', verbosity=1)
|
589
|
+
with open(source, 'rt') as fh:
|
590
|
+
data = fh.read()
|
591
|
+
else:
|
592
|
+
data = source
|
593
|
+
|
594
|
+
bdata = data.encode()
|
595
|
+
if password:
|
596
|
+
pass_data = password.encode()
|
597
|
+
else:
|
598
|
+
pass_data = None
|
599
|
+
private_key = serialization.load_pem_private_key(bdata, pass_data)
|
600
|
+
return private_key
|
601
|
+
|
602
|
+
def _sk_to_text(self, private_key, password=None, fn=None):
|
603
|
+
"""
|
604
|
+
Serialize a sk as text
|
605
|
+
|
606
|
+
Parameters
|
607
|
+
----------
|
608
|
+
private_key : sk
|
609
|
+
the secret key object.
|
610
|
+
|
611
|
+
password: str
|
612
|
+
password to be used for sk serialization
|
613
|
+
|
614
|
+
fn: str:
|
615
|
+
text file where to save the pk
|
616
|
+
|
617
|
+
Returns
|
618
|
+
-------
|
619
|
+
the sk as text string
|
620
|
+
|
621
|
+
"""
|
622
|
+
if password:
|
623
|
+
encryption_algorithm = serialization.BestAvailableEncryption(password.encode())
|
624
|
+
else:
|
625
|
+
encryption_algorithm = serialization.NoEncryption()
|
626
|
+
|
627
|
+
pem = private_key.private_bytes(
|
628
|
+
encoding=serialization.Encoding.PEM,
|
629
|
+
format=serialization.PrivateFormat.PKCS8,
|
630
|
+
encryption_algorithm=encryption_algorithm
|
631
|
+
)
|
632
|
+
str_pem = pem.decode()
|
633
|
+
if fn is not None:
|
634
|
+
full_path = os.path.abspath(fn)
|
635
|
+
self.P("Writing PEM-encoded key to {}".format(full_path), color='g', verbosity=2)
|
636
|
+
with open(fn, 'wt') as fh:
|
637
|
+
fh.write(str_pem)
|
638
|
+
return str_pem
|
639
|
+
|
640
|
+
|
641
|
+
def _dict_to_json(self, dct_data, replace_nan=True, inplace=True):
|
642
|
+
if replace_nan:
|
643
|
+
dct_safe_data = replace_nan_inf(dct_data, inplace=inplace)
|
644
|
+
else:
|
645
|
+
dct_safe_data = dct_data
|
646
|
+
|
647
|
+
dumps_config = dict(
|
648
|
+
sort_keys=True,
|
649
|
+
cls=_ComplexJsonEncoder,
|
650
|
+
separators=(',',':'),
|
651
|
+
ensure_ascii=self.__ensure_ascii_payloads,
|
652
|
+
)
|
653
|
+
str_data = json.dumps(dct_safe_data, **dumps_config)
|
654
|
+
return str_data
|
655
|
+
|
656
|
+
def _create_new_sk(self):
|
657
|
+
"""
|
658
|
+
Simple wrapper to generated pk
|
659
|
+
|
660
|
+
|
661
|
+
Returns
|
662
|
+
-------
|
663
|
+
private_key : sk
|
664
|
+
|
665
|
+
"""
|
666
|
+
raise NotImplementedError()
|
667
|
+
|
668
|
+
|
669
|
+
|
670
|
+
def _verify(self, public_key, signature : bytes, data : bytes):
|
671
|
+
"""
|
672
|
+
Verifies a `pk` signature on a binary `data` package
|
673
|
+
|
674
|
+
|
675
|
+
Parameters
|
676
|
+
----------
|
677
|
+
public_key : pk type
|
678
|
+
the pk object.
|
679
|
+
|
680
|
+
signature : bytes
|
681
|
+
the binary signature.
|
682
|
+
|
683
|
+
data : bytes
|
684
|
+
the binary message.
|
685
|
+
|
686
|
+
|
687
|
+
Returns
|
688
|
+
-------
|
689
|
+
result: _DotDict
|
690
|
+
contains `result.ok` and `result.message`
|
691
|
+
|
692
|
+
"""
|
693
|
+
raise NotImplementedError()
|
694
|
+
|
695
|
+
|
696
|
+
|
697
|
+
def _sign(self, data : bytes, private_key, text=False):
|
698
|
+
"""
|
699
|
+
Sign a binary message with Elliptic Curve
|
700
|
+
|
701
|
+
|
702
|
+
Parameters
|
703
|
+
----------
|
704
|
+
data : bytes
|
705
|
+
the binary message.
|
706
|
+
|
707
|
+
private_key : pk
|
708
|
+
the private key object.
|
709
|
+
|
710
|
+
text : bool, optional
|
711
|
+
return the signature as text. The default is False.
|
712
|
+
|
713
|
+
Returns
|
714
|
+
-------
|
715
|
+
signature as text or binary
|
716
|
+
|
717
|
+
"""
|
718
|
+
raise NotImplementedError()
|
719
|
+
|
720
|
+
|
721
|
+
|
722
|
+
#############################################################################
|
723
|
+
#### ####
|
724
|
+
#### Public functions ####
|
725
|
+
#### ####
|
726
|
+
#############################################################################
|
727
|
+
|
728
|
+
@property
|
729
|
+
def address(self):
|
730
|
+
"""Returns the public address"""
|
731
|
+
return self.__address
|
732
|
+
|
733
|
+
@property
|
734
|
+
def address_no_prefix(self):
|
735
|
+
"""Returns the public address without the prefix"""
|
736
|
+
return self._remove_prefix(self.address)
|
737
|
+
|
738
|
+
@property
|
739
|
+
def allowed_list(self):
|
740
|
+
"""Returns the allowed command senders for the current node"""
|
741
|
+
return self._load_and_maybe_create_allowed()
|
742
|
+
|
743
|
+
@property
|
744
|
+
def whitelist(self):
|
745
|
+
"""Returns the allowed command senders for the current node"""
|
746
|
+
return self.allowed_list
|
747
|
+
|
748
|
+
|
749
|
+
def maybe_remove_prefix(self, address):
|
750
|
+
"""
|
751
|
+
Removes the prefix from the address
|
752
|
+
|
753
|
+
Parameters
|
754
|
+
----------
|
755
|
+
address : str
|
756
|
+
the text address.
|
757
|
+
|
758
|
+
Returns
|
759
|
+
-------
|
760
|
+
address : str
|
761
|
+
the address without the prefix.
|
762
|
+
"""
|
763
|
+
return self._remove_prefix(address)
|
764
|
+
|
765
|
+
|
766
|
+
def dict_digest(self, dct_data, return_str=True):
|
767
|
+
"""Generates the hash of a dict object given as parameter"""
|
768
|
+
str_data = self._dict_to_json(dct_data, replace_nan=True)
|
769
|
+
bin_hex_hash, hex_hash = self._compute_hash(str_data.encode())
|
770
|
+
if return_str:
|
771
|
+
return hex_hash
|
772
|
+
else:
|
773
|
+
return bin_hex_hash
|
774
|
+
|
775
|
+
|
776
|
+
def save_sk(self, fn, password=None):
|
777
|
+
"""
|
778
|
+
Saves the SK with or without password
|
779
|
+
|
780
|
+
Parameters
|
781
|
+
----------
|
782
|
+
fn : str
|
783
|
+
SK file name.
|
784
|
+
password : str, optional
|
785
|
+
optional password. The default is None.
|
786
|
+
|
787
|
+
Returns
|
788
|
+
-------
|
789
|
+
fn : str
|
790
|
+
saved file name.
|
791
|
+
|
792
|
+
"""
|
793
|
+
self.P("Serializing the private key...", verbosity=2)
|
794
|
+
_ = self._sk_to_text(
|
795
|
+
private_key=self.__private_key,
|
796
|
+
password=password,
|
797
|
+
fn=fn
|
798
|
+
)
|
799
|
+
return fn
|
800
|
+
|
801
|
+
|
802
|
+
def _generate_data_for_hash(self, dct_data, replace_nan=True):
|
803
|
+
"""
|
804
|
+
Will convert the dict to json (removing the non-data fields) and return the json string.
|
805
|
+
The dict will be modified inplace to replace NaN and Inf with None.
|
806
|
+
"""
|
807
|
+
assert isinstance(dct_data, dict), "Cannot compute hash on non-dict data"
|
808
|
+
dct_only_data = {k:dct_data[k] for k in dct_data if k not in NON_DATA_FIELDS}
|
809
|
+
str_data = self._dict_to_json(
|
810
|
+
dct_only_data,
|
811
|
+
replace_nan=replace_nan,
|
812
|
+
inplace=True # will replace inplace the np.nan and np.inf with None
|
813
|
+
)
|
814
|
+
return str_data
|
815
|
+
|
816
|
+
|
817
|
+
def compute_hash(self, dct_data, return_all=False, replace_nan=True):
|
818
|
+
"""
|
819
|
+
Computes the hash of a dict object
|
820
|
+
|
821
|
+
Parameters
|
822
|
+
----------
|
823
|
+
dct_data : dict
|
824
|
+
the input message as a dict.
|
825
|
+
|
826
|
+
return_all: bool, optional
|
827
|
+
if `True` will return the binary hash as well. Default `False`
|
828
|
+
|
829
|
+
replace_nan: bool, optional
|
830
|
+
will replace inplace `np.nan` and `np.inf` with `None` before hashing. Default `True`
|
831
|
+
|
832
|
+
Returns
|
833
|
+
-------
|
834
|
+
result : str or tuple(bytes, bytes, str) if `return_all` is `True`
|
835
|
+
|
836
|
+
"""
|
837
|
+
str_data = self._generate_data_for_hash(dct_data, replace_nan=replace_nan)
|
838
|
+
bdata = bytes(str_data, 'utf-8')
|
839
|
+
bin_hexdigest, hexdigest = self._compute_hash(bdata)
|
840
|
+
if return_all:
|
841
|
+
result = bdata, bin_hexdigest, hexdigest
|
842
|
+
else:
|
843
|
+
result = hexdigest
|
844
|
+
return result
|
845
|
+
|
846
|
+
|
847
|
+
def sign(self, dct_data: dict, add_data=True, use_digest=True, replace_nan=True) -> str:
|
848
|
+
"""
|
849
|
+
Generates the signature for a dict object.
|
850
|
+
Does not add the signature to the dict object
|
851
|
+
|
852
|
+
|
853
|
+
Parameters
|
854
|
+
----------
|
855
|
+
dct_data : dict
|
856
|
+
the input message as a dict.
|
857
|
+
|
858
|
+
add_data: bool, optional
|
859
|
+
will add signature and address to the data dict (also digest if required). Default `True`
|
860
|
+
|
861
|
+
use_digest: bool, optional
|
862
|
+
will compute data hash and sign only on hash
|
863
|
+
|
864
|
+
replace_nan: bool, optional
|
865
|
+
will replace `np.nan` and `np.inf` with `None` before signing.
|
866
|
+
|
867
|
+
Returns
|
868
|
+
-------
|
869
|
+
text signature
|
870
|
+
|
871
|
+
|
872
|
+
IMPORTANT:
|
873
|
+
It is quite probable that the same sign(sk, hash) will generate different signatures
|
874
|
+
|
875
|
+
"""
|
876
|
+
result = None
|
877
|
+
assert isinstance(dct_data, dict), "Cannot sign on non-dict data"
|
878
|
+
|
879
|
+
bdata, bin_hexdigest, hexdigest = self.compute_hash(
|
880
|
+
dct_data,
|
881
|
+
return_all=True,
|
882
|
+
replace_nan=replace_nan,
|
883
|
+
)
|
884
|
+
if use_digest:
|
885
|
+
bdata = bin_hexdigest # to-sign data is the hash
|
886
|
+
# finally sign either full or just hash
|
887
|
+
result = self._sign(data=bdata, private_key=self.__private_key, text=True)
|
888
|
+
if add_data:
|
889
|
+
# not populate dict
|
890
|
+
dct_data[BCct.SIGN] = result
|
891
|
+
dct_data[BCct.SENDER] = self.address
|
892
|
+
if use_digest:
|
893
|
+
dct_data[BCct.HASH] = hexdigest
|
894
|
+
return result
|
895
|
+
|
896
|
+
|
897
|
+
|
898
|
+
def verify(
|
899
|
+
self,
|
900
|
+
dct_data: dict,
|
901
|
+
signature: str=None,
|
902
|
+
sender_address: str=None,
|
903
|
+
return_full_info=True,
|
904
|
+
verify_allowed=False,
|
905
|
+
replace_nan=True,
|
906
|
+
log_hash_sign_fails=True,
|
907
|
+
) -> bool:
|
908
|
+
"""
|
909
|
+
Verifies the signature validity of a given text message
|
910
|
+
|
911
|
+
Parameters
|
912
|
+
----------
|
913
|
+
dct_data : dict
|
914
|
+
dict object that needs to be verified against the signature.
|
915
|
+
|
916
|
+
signature : str, optional
|
917
|
+
the text encoded signature. Extracted from dict if missing
|
918
|
+
|
919
|
+
sender_address : str, optional
|
920
|
+
the text encoded public key. Extracted from dict if missing
|
921
|
+
|
922
|
+
return_full_info: bool, optional
|
923
|
+
if `True` will return more than `True/False` for signature verification
|
924
|
+
|
925
|
+
verify_allowed: bool, optional
|
926
|
+
if true will also check if the address is allowed by calling `check_allowed`
|
927
|
+
|
928
|
+
replace_nan: bool, optional
|
929
|
+
will replace `np.nan` and `np.inf` with `None` before verifying. Default `True`
|
930
|
+
|
931
|
+
log_hash_sign_fails: bool, optional
|
932
|
+
if `True` will log the verification failures for hash and signature issues. Default `True`
|
933
|
+
|
934
|
+
|
935
|
+
Returns
|
936
|
+
-------
|
937
|
+
bool / VerifyMessage
|
938
|
+
returns `True` if signature verifies else `False`. returns `VerifyMessage` if return_full_info
|
939
|
+
|
940
|
+
"""
|
941
|
+
result = False
|
942
|
+
|
943
|
+
bdata_json, bin_hexdigest, hexdigest = self.compute_hash(
|
944
|
+
dct_data,
|
945
|
+
return_all=True,
|
946
|
+
replace_nan=replace_nan,
|
947
|
+
)
|
948
|
+
|
949
|
+
if signature is None:
|
950
|
+
signature = dct_data.get(BCct.SIGN)
|
951
|
+
|
952
|
+
if sender_address is None:
|
953
|
+
sender_address = dct_data.get(BCct.SENDER)
|
954
|
+
|
955
|
+
verify_msg = VerifyMessage()
|
956
|
+
verify_msg.sender = sender_address
|
957
|
+
|
958
|
+
received_digest = dct_data.get(BCct.HASH)
|
959
|
+
if received_digest:
|
960
|
+
# we need to verify hash and then verify signature on hash
|
961
|
+
if hexdigest != received_digest:
|
962
|
+
verify_msg.message = "Corrupted digest!"
|
963
|
+
verify_msg.valid = False
|
964
|
+
#endif hash failed
|
965
|
+
bdata = bin_hexdigest
|
966
|
+
else:
|
967
|
+
# normal signature on data
|
968
|
+
bdata = bdata_json
|
969
|
+
#endif has hash or not
|
970
|
+
|
971
|
+
if verify_msg.message is None:
|
972
|
+
try:
|
973
|
+
assert sender_address is not None, 'Sender address is NULL'
|
974
|
+
assert signature is not None, 'Signature is NULL'
|
975
|
+
|
976
|
+
bsignature = self._text_to_binary(signature)
|
977
|
+
pk = self._address_to_pk(sender_address)
|
978
|
+
verify_msg = self._verify(public_key=pk, signature=bsignature, data=bdata)
|
979
|
+
except Exception as exc:
|
980
|
+
verify_msg.message = str(exc)
|
981
|
+
verify_msg.valid = False
|
982
|
+
#endif check if signature failed already from digesting
|
983
|
+
|
984
|
+
verify_msg.sender = sender_address
|
985
|
+
|
986
|
+
if not verify_msg.valid:
|
987
|
+
if log_hash_sign_fails and signature is not None and sender_address is not None:
|
988
|
+
self.P("Signature failed on msg from {}: {}".format(
|
989
|
+
sender_address, verify_msg.message
|
990
|
+
), color='r', verbosity=1,
|
991
|
+
)
|
992
|
+
elif verify_allowed and verify_msg.valid:
|
993
|
+
if not self.is_allowed(sender_address):
|
994
|
+
verify_msg.message = "Signature ok but address {} not in {}.".format(sender_address, BCct.AUTHORISED_ADDRS)
|
995
|
+
verify_msg.valid = False
|
996
|
+
#endif not allowed
|
997
|
+
#endif ok but authorization required
|
998
|
+
|
999
|
+
if return_full_info:
|
1000
|
+
result = verify_msg
|
1001
|
+
else:
|
1002
|
+
result = verify_msg.ok
|
1003
|
+
return result
|
1004
|
+
|
1005
|
+
|
1006
|
+
def is_allowed(self, sender_address: str):
|
1007
|
+
to_search_address = self._remove_prefix(sender_address)
|
1008
|
+
is_allowed = to_search_address in self.allowed_list or to_search_address == self._remove_prefix(self.address)
|
1009
|
+
return is_allowed
|
1010
|
+
|
1011
|
+
|
1012
|
+
def encrypt(self, data, destination):
|
1013
|
+
"""
|
1014
|
+
Encrypts the data for a given destination
|
1015
|
+
|
1016
|
+
Parameters
|
1017
|
+
----------
|
1018
|
+
data : dict
|
1019
|
+
the data to be encrypted.
|
1020
|
+
|
1021
|
+
destination : str
|
1022
|
+
the destination address.
|
1023
|
+
|
1024
|
+
Returns
|
1025
|
+
-------
|
1026
|
+
None.
|
1027
|
+
|
1028
|
+
"""
|
1029
|
+
raise NotImplementedError()
|
1030
|
+
|
1031
|
+
def decrypt(self, data):
|
1032
|
+
"""
|
1033
|
+
Decrypts the data
|
1034
|
+
|
1035
|
+
Parameters
|
1036
|
+
----------
|
1037
|
+
data : dict
|
1038
|
+
the data to be decrypted.
|
1039
|
+
|
1040
|
+
Returns
|
1041
|
+
-------
|
1042
|
+
None.
|
1043
|
+
|
1044
|
+
"""
|
1045
|
+
raise NotImplementedError()
|
1046
|
+
|