naeural-client 2.7.27__py3-none-any.whl → 2.7.29__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/_ver.py CHANGED
@@ -1,4 +1,4 @@
1
- __VER__ = "2.7.27"
1
+ __VER__ = "2.7.29"
2
2
 
3
3
  if __name__ == "__main__":
4
4
  with open("pyproject.toml", "rt") as fd:
naeural_client/bc/base.py CHANGED
@@ -7,7 +7,6 @@ import datetime
7
7
  import uuid
8
8
  import requests
9
9
 
10
- from web3 import Web3
11
10
 
12
11
  from hashlib import sha256, md5
13
12
  from threading import Lock
@@ -24,7 +23,10 @@ from ..const.base import (
24
23
  DAUTH_NONCE, dAuth,
25
24
  )
26
25
 
27
-
26
+ from .evm import _EVMMixin, Web3, EE_VPN_IMPL
27
+
28
+ EVM_COMMENT = " # "
29
+ INVALID_COMMENT = " # INVALID: "
28
30
 
29
31
 
30
32
  class _DotDict(dict):
@@ -238,7 +240,7 @@ def ripemd160(data):
238
240
 
239
241
  # END ## RIPEMD160
240
242
 
241
- class BaseBlockEngine:
243
+ class BaseBlockEngine(_EVMMixin):
242
244
  """
243
245
  This multiton (multi-singleton via key) is the base workhorse of the private blockchain.
244
246
 
@@ -326,7 +328,8 @@ class BaseBlockEngine:
326
328
  self.__pem_file = pem_fn
327
329
  self._init()
328
330
  return
329
-
331
+
332
+
330
333
  def P(self, s, color=None, boxed=False, verbosity=1, **kwargs):
331
334
  if verbosity > self.__verbosity:
332
335
  return
@@ -338,16 +341,36 @@ class BaseBlockEngine:
338
341
  boxed=boxed,
339
342
  **kwargs
340
343
  )
344
+ return
341
345
 
342
346
 
343
347
  @property
344
348
  def eth_enabled(self):
345
349
  return self.__eth_enabled
346
-
350
+
351
+
352
+ def set_eth_flag(self, value):
353
+ if value != self.__eth_enabled:
354
+ self.__eth_enabled = value
355
+ self.log.P("Changed eth_enabled to {}".format(value), color='d')
356
+ return
357
+
358
+
347
359
  @property
348
360
  def name(self):
349
361
  return self.__name
350
362
 
363
+
364
+ @property
365
+ def eth_address(self):
366
+ return self.__eth_address
367
+
368
+
369
+ @property
370
+ def eth_account(self):
371
+ return self.__eth_account
372
+
373
+
351
374
  def _init(self):
352
375
  self.P(
353
376
  f"Initializing BC-engine (ETH_ENABLED={self.__eth_enabled})...", verbosity=1
@@ -523,7 +546,7 @@ class BaseBlockEngine:
523
546
  return path
524
547
 
525
548
 
526
- def address_is_valid(self, address):
549
+ def address_is_valid(self, address, return_error=False):
527
550
  """
528
551
  Checks if an address is valid
529
552
 
@@ -539,11 +562,15 @@ class BaseBlockEngine:
539
562
 
540
563
  """
541
564
  result = False
565
+ msg = ""
542
566
  try:
543
567
  pk = self._address_to_pk(address)
544
568
  result = False if pk is None else True
545
- except:
569
+ except Exception as exc:
546
570
  result = False
571
+ msg = str(exc)
572
+ if return_error:
573
+ return result, msg
547
574
  return result
548
575
 
549
576
 
@@ -558,8 +585,10 @@ class BaseBlockEngine:
558
585
  if isinstance(address, list) and len(address) > 0:
559
586
  # self.P(f"Adding addresses to the allowed list:\n{address}", verbosity=1)
560
587
  # now check addresses
561
- lst_addresses = []
588
+ lst_lines = []
589
+ lst_addrs = []
562
590
  lst_names = []
591
+ whitelist = self.whitelist_with_prefixes
563
592
  for addr in address:
564
593
  addr = addr.strip()
565
594
  parts = addr.split()
@@ -567,31 +596,45 @@ class BaseBlockEngine:
567
596
  continue
568
597
  addr = parts[0]
569
598
  name = parts[1] if len(parts) > 1 else ""
570
- if not self.address_is_valid(addr):
599
+ is_valid, valid_msg = self.address_is_valid(addr, return_error=True)
600
+ if not is_valid:
571
601
  self.P("WARNING: address <{}> is not valid. Ignoring.".format(addr), color='r')
602
+ addr = "# " + addr
603
+ name = name + INVALID_COMMENT + valid_msg
604
+ continue # skip invalid address or go forward and add it ...
572
605
  else:
573
- lst_addresses.append(self.maybe_add_prefix(addr))
574
- lst_names.append(name)
606
+ addr = self.maybe_add_prefix(addr)
607
+ if addr in whitelist:
608
+ self.P("WARNING: address <{}> already in the allowed list. Ignoring.".format(addr), color='r')
609
+ continue
610
+ eth = self.node_address_to_eth_address(addr)
611
+ name = name + EVM_COMMENT + eth
612
+ str_line = "{}{}".format(addr, (" " + name) if len(name)>0 else "")
613
+ lst_lines.append(str_line)
614
+ lst_addrs.append(addr)
615
+ lst_names.append(name)
575
616
  #endif
576
617
  #endfor
577
- if len(lst_addresses) > 0:
578
- existing_addrs, existing_names = self._load_and_maybe_create_allowed(
579
- return_names=True, return_prefix=True
580
- )
581
- for addr, name in zip(lst_addresses, lst_names):
582
- if addr not in existing_addrs:
618
+ if len(lst_lines) > 0:
619
+ with self._whitelist_lock:
620
+ fn = self._get_allowed_file()
621
+ with open(fn, 'rt') as fh:
622
+ lst_existing = fh.readlines()
623
+ #endwith
624
+ for line, addr, name in zip(lst_lines, lst_addrs, lst_names):
625
+ if line not in lst_existing:
583
626
  changed = True
584
- existing_addrs.append(addr)
585
- existing_names.append(name)
586
- self.P("Address <{}{}> added to the allowed list.".format(addr, name), color='g')
587
- #endif new address
627
+ lst_existing.append(line)
628
+ self.P("Address <{}> added to the allowed list.".format(addr), color='g')
588
629
  #endfor
589
630
  if changed:
590
631
  with self._whitelist_lock:
591
632
  fn = self._get_allowed_file()
592
633
  with open(fn, 'wt') as fh:
593
- for addr, name in zip(existing_addrs, existing_names):
594
- fh.write("{}{}\n".format(addr, (" " + name) if len(name)>0 else ""))
634
+ for line in lst_existing:
635
+ line = line.strip()
636
+ if line != "":
637
+ fh.write(f"{line}\n")
595
638
  #endfor each address in modified whitelist
596
639
  #endwith open file
597
640
  #endwith lock
@@ -617,30 +660,42 @@ class BaseBlockEngine:
617
660
  fh.write('\n')
618
661
  lst_allowed = [x.strip() for x in lst_allowed]
619
662
  lst_allowed = [x for x in lst_allowed if x != '']
620
- lst_lines = []
621
- errors = False
663
+ lst_lines_to_write = []
664
+ needs_rewrite = False
622
665
  for allowed_tuple in lst_allowed:
623
666
  parts = allowed_tuple.split()
624
667
  if len(parts) == 0:
625
668
  continue
626
669
  allowed = parts[0]
670
+ if allowed.startswith("#"):
671
+ # skip comments but keep them if we re-write the file
672
+ lst_lines_to_write.append(allowed_tuple)
673
+ continue
627
674
  allowed = self._remove_prefix(allowed)
628
675
  name = parts[1] if len(parts) > 1 else ""
629
- if not self.address_is_valid(allowed):
630
- self.P("WARNING: address <{}> is not valid. Removing {} from allowed list.".format(
676
+ is_valid, valid_msg = self.address_is_valid(allowed, return_error=True)
677
+ if not is_valid:
678
+ self.P("WARNING: address <{}> is not valid. Commenting {} from allowed list.".format(
631
679
  allowed, allowed_tuple), color='r'
632
680
  )
633
- errors = True
681
+ needs_rewrite = True
682
+ error_line = "# " + allowed_tuple + INVALID_COMMENT + valid_msg
683
+ lst_lines_to_write.append(error_line)
634
684
  else:
635
685
  if return_prefix:
636
686
  allowed = self.maybe_add_prefix(allowed)
637
687
  lst_final.append(allowed)
638
- lst_lines.append(allowed_tuple)
639
688
  lst_names.append(name)
640
- if errors:
689
+ if len(parts) < 3:
690
+ eth = self.node_address_to_eth_address(allowed)
691
+ allowed_tuple = allowed_tuple + EVM_COMMENT + eth
692
+ needs_rewrite = True
693
+ lst_lines_to_write.append(allowed_tuple)
694
+
695
+ if needs_rewrite:
641
696
  with open(fn, 'wt') as fh:
642
- for line in lst_lines:
643
- fh.write("{}\n".format(line))
697
+ for line in lst_lines_to_write:
698
+ fh.write(f"{line}\n")
644
699
  except Exception as exc:
645
700
  self.P(f"ERROR: failed to load the allowed list of addresses: {exc}", color='r')
646
701
  #endtry
@@ -899,32 +954,6 @@ class BaseBlockEngine:
899
954
  """
900
955
  raise NotImplementedError()
901
956
 
902
-
903
- def _get_eth_address(self):
904
- """
905
- Returns the Ethereum address for the current pk
906
-
907
- Returns
908
- -------
909
- eth_address : str
910
- the Ethereum address.
911
-
912
- """
913
- raise NotImplementedError()
914
-
915
-
916
- def _get_eth_acccount(self):
917
- """
918
- Returns the Ethereum account for the current sk
919
-
920
- Returns
921
- -------
922
- eth_account : str
923
- the Ethereum account.
924
-
925
- """
926
- raise NotImplementedError()
927
-
928
957
 
929
958
 
930
959
  #############################################################################
@@ -1346,161 +1375,7 @@ class BaseBlockEngine:
1346
1375
  raise NotImplementedError()
1347
1376
 
1348
1377
 
1349
- ### Ethereum
1350
-
1351
- def set_eth_flag(self, value):
1352
- if value != self.__eth_enabled:
1353
- self.__eth_enabled = value
1354
- self.log.P("Changed eth_enabled to {}".format(value), color='d')
1355
- return
1356
-
1357
- @property
1358
- def eth_address(self):
1359
- return self.__eth_address
1360
-
1361
- @property
1362
- def eth_account(self):
1363
- return self.__eth_account
1364
1378
 
1365
- @staticmethod
1366
- def is_valid_evm_address(address: str) -> bool:
1367
- """
1368
- Check if the input string is a valid Ethereum (EVM) address using basic heuristics.
1369
-
1370
- Parameters
1371
- ----------
1372
- address : str
1373
- The address string to verify.
1374
-
1375
- Returns
1376
- -------
1377
- bool
1378
- True if `address` meets the basic criteria for an EVM address, False otherwise.
1379
- """
1380
- # Basic checks:
1381
- # A) Must start with '0x'
1382
- # B) Must be exactly 42 characters in total
1383
- # C) All remaining characters must be valid hexadecimal digits
1384
- if not address.startswith("0x"):
1385
- return False
1386
- if len(address) != 42:
1387
- return False
1388
-
1389
- hex_part = address[2:]
1390
- # Ensure all characters in the hex part are valid hex digits
1391
- return all(c in "0123456789abcdefABCDEF" for c in hex_part)
1392
-
1393
- @staticmethod
1394
- def is_valid_eth_address(address: str) -> bool:
1395
- """
1396
- Check if the input string is a valid Ethereum (EVM) address using basic heuristics.
1397
-
1398
- Parameters
1399
- ----------
1400
- address : str
1401
- The address string to verify.
1402
-
1403
- Returns
1404
- -------
1405
- bool
1406
- True if `address` meets the basic criteria for an EVM address, False otherwise.
1407
- """
1408
- return BaseBlockEngine.is_valid_evm_address(address)
1409
-
1410
-
1411
- @staticmethod
1412
- def get_evm_network() -> str:
1413
- """
1414
- Get the current network
1415
-
1416
- Returns
1417
- -------
1418
- str
1419
- the network name.
1420
-
1421
- """
1422
- return os.environ.get(dAuth.DAUTH_NET_ENV_KEY, dAuth.DAUTH_SDK_NET_DEFAULT)
1423
-
1424
- @property
1425
- def evm_network(self):
1426
- return self.get_evm_network()
1427
-
1428
- def get_network_data(self, network=None):
1429
- assert isinstance(network, str) and network.lower() in dAuth.EVM_NET_DATA, f"Invalid network: {network}"
1430
- return dAuth.EVM_NET_DATA[network.lower()]
1431
-
1432
-
1433
- def web3_is_node_licensed(self, address : str, network=None, debug=False) -> bool:
1434
- """
1435
- Check if the address is allowed to send commands to the node
1436
-
1437
- Parameters
1438
- ----------
1439
- address : str
1440
- the address to check.
1441
- """
1442
- if network is None:
1443
- network = self.evm_network
1444
-
1445
- assert BaseBlockEngine.is_valid_eth_address(address), "Invalid Ethereum address"
1446
-
1447
- network_data = self.get_network_data(network)
1448
-
1449
- contract_address = network_data[dAuth.EvmNetData.DAUTH_ND_ADDR_KEY]
1450
- rpc_url = network_data[dAuth.EvmNetData.DAUTH_RPC_KEY]
1451
-
1452
- if debug:
1453
- self.P(f"Checking if {address} ({network}) is allowed via {rpc_url}...")
1454
-
1455
- w3 = Web3(Web3.HTTPProvider(rpc_url))
1456
-
1457
- contract_abi = dAuth.DAUTH_ABI_IS_NODE_ACTIVE
1458
-
1459
- contract = w3.eth.contract(address=contract_address, abi=contract_abi)
1460
-
1461
- result = contract.functions.isNodeActive(address).call()
1462
- return result
1463
-
1464
-
1465
- def web3_get_oracles(self, network=None, debug=False) -> list:
1466
- """
1467
- Get the list of oracles from the contract
1468
-
1469
- Parameters
1470
- ----------
1471
- network : str, optional
1472
- the network to use. The default is None.
1473
-
1474
- Returns
1475
- -------
1476
- list
1477
- the list of oracles addresses.
1478
-
1479
- """
1480
- if network is None:
1481
- network = BaseBlockEngine.get_evm_network()
1482
-
1483
- network_data = self.get_network_data(network)
1484
-
1485
- contract_address = network_data[dAuth.EvmNetData.DAUTH_ND_ADDR_KEY]
1486
- rpc_url = network_data[dAuth.EvmNetData.DAUTH_RPC_KEY]
1487
-
1488
- if debug:
1489
- self.P(f"Getting oracles for {network} via {rpc_url}...")
1490
-
1491
- w3 = Web3(Web3.HTTPProvider(rpc_url))
1492
-
1493
- contract_abi = dAuth.DAUTH_ABI_GET_SIGNERS
1494
-
1495
- contract = w3.eth.contract(address=contract_address, abi=contract_abi)
1496
-
1497
- result = contract.functions.getSigners().call()
1498
- return result
1499
-
1500
-
1501
-
1502
- ### end Ethereum
1503
-
1504
1379
 
1505
1380
  def dauth_autocomplete(
1506
1381
  self,
@@ -1510,6 +1385,7 @@ class BaseBlockEngine:
1510
1385
  max_tries=5,
1511
1386
  network=None,
1512
1387
  return_full_data=False,
1388
+ debug_data=False,
1513
1389
  **kwargs
1514
1390
  ):
1515
1391
  """
@@ -1529,6 +1405,9 @@ class BaseBlockEngine:
1529
1405
  dict with the dAuth information if the request got status 200(if errors occured, but
1530
1406
  the status is still 200, an empty dictionary will be returned).
1531
1407
  """
1408
+ if EE_VPN_IMPL:
1409
+ return {}
1410
+ #endif EE_VPN_IMPL
1532
1411
  from naeural_client._ver import __VER__ as sdk_version
1533
1412
  try:
1534
1413
  from ver import __VER__ as app_version
@@ -1575,19 +1454,31 @@ class BaseBlockEngine:
1575
1454
  while not done:
1576
1455
  self.P(f"<{eth_short}> ({network}) dAuth with `{url}`... (try {tries + 1} / {max_tries})")
1577
1456
  try:
1578
- to_send = {
1579
- **kwargs,
1580
- DAUTH_NONCE : str(uuid.uuid4())[:8],
1581
- dAuth.DAUTH_SENDER_APP_VER : app_version,
1582
- dAuth.DAUTH_SENDER_SDK_VER : sdk_version,
1583
- dAuth.DAUTH_SENDER_CORE_VER : core_version,
1584
- }
1457
+ if debug_data:
1458
+ to_send = {
1459
+ DAUTH_NONCE : str(uuid.uuid4())[:8],
1460
+ dAuth.DAUTH_SENDER_APP_VER : app_version,
1461
+ dAuth.DAUTH_SENDER_SDK_VER : sdk_version,
1462
+ dAuth.DAUTH_SENDER_CORE_VER : core_version,
1463
+ **kwargs,
1464
+ }
1465
+ else:
1466
+ to_send = {
1467
+ **kwargs,
1468
+ DAUTH_NONCE : str(uuid.uuid4())[:8],
1469
+ dAuth.DAUTH_SENDER_APP_VER : app_version,
1470
+ dAuth.DAUTH_SENDER_SDK_VER : sdk_version,
1471
+ dAuth.DAUTH_SENDER_CORE_VER : core_version,
1472
+ }
1585
1473
  ######
1586
1474
  if len(kwargs) == 0:
1587
1475
  to_send[dAuth.DAUTH_SENDER_ALIAS] = dAuth.DAUTH_SENDER_ALIAS_DEFAULT
1588
1476
  ######
1589
- self.sign(to_send)
1590
- response = requests.post(url, json={'body' : to_send})
1477
+ self.sign(to_send)
1478
+ json_to_send = {'body' : to_send}
1479
+ if debug:
1480
+ self.P(f"Sending to dAuth URL: {url}\n{json.dumps(json_to_send, indent=2)}")
1481
+ response = requests.post(url, json=json_to_send)
1591
1482
  if response.status_code == 200:
1592
1483
  dct_response = response.json()
1593
1484
  dct_result = dct_response.get('result', {}) or {}
naeural_client/bc/ec.py CHANGED
@@ -12,11 +12,9 @@ from cryptography.hazmat.backends import default_backend
12
12
  from cryptography.hazmat.primitives.ciphers.aead import AESGCM
13
13
  from cryptography.hazmat.primitives.kdf.hkdf import HKDF
14
14
 
15
- from eth_account import Account
16
- from eth_utils import keccak, to_checksum_address
17
- from eth_account.messages import encode_defunct
18
15
 
19
- from .base import BaseBlockEngine, VerifyMessage, BCct, Web3
16
+
17
+ from .base import BaseBlockEngine, VerifyMessage, BCct
20
18
 
21
19
 
22
20
 
@@ -669,192 +667,3 @@ class BaseBCEllipticCurveEngine(BaseBlockEngine):
669
667
 
670
668
  ## end multi destination encryption
671
669
 
672
-
673
- ### ETH
674
-
675
- def _get_eth_address(self, pk=None):
676
- if pk is None:
677
- pk = self.public_key
678
- raw_public_key = pk.public_numbers()
679
-
680
- # Compute Ethereum-compatible address
681
- x = raw_public_key.x.to_bytes(32, 'big')
682
- y = raw_public_key.y.to_bytes(32, 'big')
683
- uncompressed_key = b'\x04' + x + y
684
- keccak_hash = keccak(uncompressed_key[1:]) # Remove 0x04 prefix
685
- eth_address = "0x" + keccak_hash[-20:].hex()
686
- eth_address = to_checksum_address(eth_address)
687
- return eth_address
688
-
689
- def _get_eth_account(self):
690
- private_key_bytes = self.private_key.private_numbers().private_value.to_bytes(32, 'big')
691
- return Account.from_key(private_key_bytes)
692
-
693
-
694
- def node_address_to_eth_address(self, address):
695
- """
696
- Converts a node address to an Ethereum address.
697
-
698
- Parameters
699
- ----------
700
- address : str
701
- The node address convert.
702
-
703
- Returns
704
- -------
705
- str
706
- The Ethereum address.
707
- """
708
- public_key = self._address_to_pk(address)
709
- return self._get_eth_address(pk=public_key)
710
-
711
- def is_node_address_in_eth_addresses(self, node_address: str, lst_eth_addrs) -> bool:
712
- """
713
- Check if the node address is in the list of Ethereum addresses
714
-
715
- Parameters
716
- ----------
717
- node_address : str
718
- the node address.
719
-
720
- lst_eth_addrs : list
721
- list of Ethereum addresses.
722
-
723
- Returns
724
- -------
725
- bool
726
- True if the node address is in the list of Ethereum addresses.
727
-
728
- """
729
- eth_addr = self.node_address_to_eth_address(node_address)
730
- return eth_addr in lst_eth_addrs
731
-
732
-
733
- def eth_hash_message(self, types, values, as_hex=False):
734
- """
735
- Hashes a message using the keccak256 algorithm.
736
-
737
- Parameters
738
- ----------
739
- types : list
740
- The types of the values.
741
-
742
- values : list of any
743
- The values to hash.
744
-
745
- Returns
746
- -------
747
- bytes
748
- The hash of the message in hexadecimal format.
749
- """
750
- message = Web3.solidity_keccak(types, values)
751
- if as_hex:
752
- return message.hex()
753
- return message
754
-
755
-
756
- def eth_sign_message(self, types, values):
757
- """
758
- Signs a message using the private key.
759
-
760
- Parameters
761
- ----------
762
- types : list
763
- The types of the values.
764
-
765
- values : list of any
766
- The values to sign.
767
-
768
- Returns
769
- -------
770
- str
771
- The signature of the message.
772
- """
773
- message_hash = self.eth_hash_message(types, values, as_hex=False)
774
- signable_message = encode_defunct(primitive=message_hash)
775
- signed_message = Account.sign_message(signable_message, private_key=self.eth_account.key)
776
- if hasattr(signed_message, "message_hash"): # backward compatibility
777
- signed_message_hash = signed_message.message_hash
778
- else:
779
- signed_message_hash = signed_message.messageHash
780
- return {
781
- "message_hash": message_hash.hex(),
782
- "r": hex(signed_message.r),
783
- "s": hex(signed_message.s),
784
- "v": signed_message.v,
785
- "signature": signed_message.signature.hex(),
786
- "signed_message": signed_message_hash.hex(),
787
- "sender" : self.eth_address,
788
- "eth_signed_data" : types,
789
- }
790
-
791
- def eth_sign_text(self, message, signature_only=True):
792
- """
793
- Signs a text message using the private key.
794
-
795
- Parameters
796
- ----------
797
- message : str
798
- The message to sign.
799
-
800
- signature_only : bool, optional
801
- Whether to return only the signature. The default is True
802
-
803
- Returns
804
- -------
805
- str
806
- The signature of the message.
807
- """
808
- types = ["string"]
809
- values = [message]
810
- result = self.eth_sign_message(types, values)
811
- if signature_only:
812
- return result["signature"]
813
- return result
814
-
815
-
816
-
817
- def eth_sign_node_epochs(
818
- self,
819
- node,
820
- epochs,
821
- epochs_vals,
822
- signature_only=True,
823
- use_evm_node_addr=True
824
- ):
825
- """
826
- Signs the node availability
827
-
828
- Parameters
829
- ----------
830
- node : str
831
- The node address to sign. Either the node address or the Ethereum address based on `use_evm_node_addr`.
832
-
833
- epochs : list of int
834
- The epochs to sign.
835
-
836
- epochs_vals : list of int
837
- The values for each epoch.
838
-
839
- signature_only : bool, optional
840
- Whether to return only the signature. The default is True.
841
-
842
- use_evm_node_addr : bool, optional
843
- Whether to use the Ethereum address of the node. The default is True.
844
-
845
- Returns
846
- -------
847
- str
848
- The signature of the message.
849
- """
850
- if use_evm_node_addr:
851
- types = ["address", "uint256[]", "uint256[]"]
852
- else:
853
- types = ["string", "uint256[]", "uint256[]"]
854
- values = [node, epochs, epochs_vals]
855
- result = self.eth_sign_message(types, values)
856
- if signature_only:
857
- return result["signature"]
858
- return result
859
-
860
-
@@ -0,0 +1,355 @@
1
+ import os
2
+
3
+ from eth_account import Account
4
+ from eth_utils import keccak, to_checksum_address
5
+ from eth_account.messages import encode_defunct
6
+
7
+ from ..const.base import EE_VPN_IMPL_ENV_KEY, dAuth
8
+
9
+ EE_VPN_IMPL = str(os.environ.get(EE_VPN_IMPL_ENV_KEY, False)).lower() in [
10
+ 'true', '1', 'yes', 'y', 't', 'on'
11
+ ]
12
+
13
+ if EE_VPN_IMPL:
14
+ class Web3:
15
+ """
16
+ VPS enabled. Web3 is not available.
17
+ """
18
+ else:
19
+ from web3 import Web3
20
+
21
+
22
+
23
+ class _EVMMixin:
24
+
25
+
26
+ @staticmethod
27
+ def is_valid_evm_address(address: str) -> bool:
28
+ """
29
+ Check if the input string is a valid Ethereum (EVM) address using basic heuristics.
30
+
31
+ Parameters
32
+ ----------
33
+ address : str
34
+ The address string to verify.
35
+
36
+ Returns
37
+ -------
38
+ bool
39
+ True if `address` meets the basic criteria for an EVM address, False otherwise.
40
+ """
41
+ # Basic checks:
42
+ # A) Must start with '0x'
43
+ # B) Must be exactly 42 characters in total
44
+ # C) All remaining characters must be valid hexadecimal digits
45
+ if not address.startswith("0x"):
46
+ return False
47
+ if len(address) != 42:
48
+ return False
49
+
50
+ hex_part = address[2:]
51
+ # Ensure all characters in the hex part are valid hex digits
52
+ return all(c in "0123456789abcdefABCDEF" for c in hex_part)
53
+
54
+ @staticmethod
55
+ def is_valid_eth_address(address: str) -> bool:
56
+ """
57
+ Check if the input string is a valid Ethereum (EVM) address using basic heuristics.
58
+
59
+ Parameters
60
+ ----------
61
+ address : str
62
+ The address string to verify.
63
+
64
+ Returns
65
+ -------
66
+ bool
67
+ True if `address` meets the basic criteria for an EVM address, False otherwise.
68
+ """
69
+ return _EVMMixin.is_valid_evm_address(address)
70
+
71
+
72
+ @staticmethod
73
+ def get_evm_network() -> str:
74
+ """
75
+ Get the current network
76
+
77
+ Returns
78
+ -------
79
+ str
80
+ the network name.
81
+
82
+ """
83
+ return os.environ.get(dAuth.DAUTH_NET_ENV_KEY, dAuth.DAUTH_SDK_NET_DEFAULT)
84
+
85
+ @property
86
+ def evm_network(self):
87
+ return self.get_evm_network()
88
+
89
+ def get_network_data(self, network=None):
90
+ assert isinstance(network, str) and network.lower() in dAuth.EVM_NET_DATA, f"Invalid network: {network}"
91
+ return dAuth.EVM_NET_DATA[network.lower()]
92
+
93
+
94
+ def web3_is_node_licensed(self, address : str, network=None, debug=False) -> bool:
95
+ """
96
+ Check if the address is allowed to send commands to the node
97
+
98
+ Parameters
99
+ ----------
100
+ address : str
101
+ the address to check.
102
+ """
103
+ if EE_VPN_IMPL:
104
+ self.P("VPN implementation. Skipping Ethereum check.", color='r')
105
+ return False
106
+
107
+ if network is None:
108
+ network = self.evm_network
109
+
110
+ assert self.is_valid_eth_address(address), "Invalid Ethereum address"
111
+
112
+ network_data = self.get_network_data(network)
113
+
114
+ contract_address = network_data[dAuth.EvmNetData.DAUTH_ND_ADDR_KEY]
115
+ rpc_url = network_data[dAuth.EvmNetData.DAUTH_RPC_KEY]
116
+
117
+ if debug:
118
+ self.P(f"Checking if {address} ({network}) is allowed via {rpc_url}...")
119
+
120
+ w3 = Web3(Web3.HTTPProvider(rpc_url))
121
+
122
+ contract_abi = dAuth.DAUTH_ABI_IS_NODE_ACTIVE
123
+
124
+ contract = w3.eth.contract(address=contract_address, abi=contract_abi)
125
+
126
+ result = contract.functions.isNodeActive(address).call()
127
+ return result
128
+
129
+
130
+ def web3_get_oracles(self, network=None, debug=False) -> list:
131
+ """
132
+ Get the list of oracles from the contract
133
+
134
+ Parameters
135
+ ----------
136
+ network : str, optional
137
+ the network to use. The default is None.
138
+
139
+ Returns
140
+ -------
141
+ list
142
+ the list of oracles addresses.
143
+
144
+ """
145
+ if network is None:
146
+ network = self.evm_network
147
+
148
+ network_data = self.get_network_data(network)
149
+
150
+ contract_address = network_data[dAuth.EvmNetData.DAUTH_ND_ADDR_KEY]
151
+ rpc_url = network_data[dAuth.EvmNetData.DAUTH_RPC_KEY]
152
+
153
+ if debug:
154
+ self.P(f"Getting oracles for {network} via {rpc_url}...")
155
+
156
+ w3 = Web3(Web3.HTTPProvider(rpc_url))
157
+
158
+ contract_abi = dAuth.DAUTH_ABI_GET_SIGNERS
159
+
160
+ contract = w3.eth.contract(address=contract_address, abi=contract_abi)
161
+
162
+ result = contract.functions.getSigners().call()
163
+ return result
164
+
165
+
166
+
167
+ ### ETH
168
+
169
+ def _get_eth_address(self, pk=None):
170
+ if pk is None:
171
+ pk = self.public_key
172
+ raw_public_key = pk.public_numbers()
173
+
174
+ # Compute Ethereum-compatible address
175
+ x = raw_public_key.x.to_bytes(32, 'big')
176
+ y = raw_public_key.y.to_bytes(32, 'big')
177
+ uncompressed_key = b'\x04' + x + y
178
+ keccak_hash = keccak(uncompressed_key[1:]) # Remove 0x04 prefix
179
+ eth_address = "0x" + keccak_hash[-20:].hex()
180
+ eth_address = to_checksum_address(eth_address)
181
+ return eth_address
182
+
183
+ def _get_eth_account(self):
184
+ private_key_bytes = self.private_key.private_numbers().private_value.to_bytes(32, 'big')
185
+ return Account.from_key(private_key_bytes)
186
+
187
+
188
+ def node_address_to_eth_address(self, address):
189
+ """
190
+ Converts a node address to an Ethereum address.
191
+
192
+ Parameters
193
+ ----------
194
+ address : str
195
+ The node address convert.
196
+
197
+ Returns
198
+ -------
199
+ str
200
+ The Ethereum address.
201
+ """
202
+ public_key = self._address_to_pk(address)
203
+ return self._get_eth_address(pk=public_key)
204
+
205
+ def is_node_address_in_eth_addresses(self, node_address: str, lst_eth_addrs) -> bool:
206
+ """
207
+ Check if the node address is in the list of Ethereum addresses
208
+
209
+ Parameters
210
+ ----------
211
+ node_address : str
212
+ the node address.
213
+
214
+ lst_eth_addrs : list
215
+ list of Ethereum addresses.
216
+
217
+ Returns
218
+ -------
219
+ bool
220
+ True if the node address is in the list of Ethereum addresses.
221
+
222
+ """
223
+ eth_addr = self.node_address_to_eth_address(node_address)
224
+ return eth_addr in lst_eth_addrs
225
+
226
+
227
+ def eth_hash_message(self, types, values, as_hex=False):
228
+ """
229
+ Hashes a message using the keccak256 algorithm.
230
+
231
+ Parameters
232
+ ----------
233
+ types : list
234
+ The types of the values.
235
+
236
+ values : list of any
237
+ The values to hash.
238
+
239
+ Returns
240
+ -------
241
+ bytes
242
+ The hash of the message in hexadecimal format.
243
+ """
244
+ message = Web3.solidity_keccak(types, values)
245
+ if as_hex:
246
+ return message.hex()
247
+ return message
248
+
249
+
250
+ def eth_sign_message(self, types, values):
251
+ """
252
+ Signs a message using the private key.
253
+
254
+ Parameters
255
+ ----------
256
+ types : list
257
+ The types of the values.
258
+
259
+ values : list of any
260
+ The values to sign.
261
+
262
+ Returns
263
+ -------
264
+ str
265
+ The signature of the message.
266
+ """
267
+ message_hash = self.eth_hash_message(types, values, as_hex=False)
268
+ signable_message = encode_defunct(primitive=message_hash)
269
+ signed_message = Account.sign_message(signable_message, private_key=self.eth_account.key)
270
+ if hasattr(signed_message, "message_hash"): # backward compatibility
271
+ signed_message_hash = signed_message.message_hash
272
+ else:
273
+ signed_message_hash = signed_message.messageHash
274
+ return {
275
+ "message_hash": message_hash.hex(),
276
+ "r": hex(signed_message.r),
277
+ "s": hex(signed_message.s),
278
+ "v": signed_message.v,
279
+ "signature": signed_message.signature.hex(),
280
+ "signed_message": signed_message_hash.hex(),
281
+ "sender" : self.eth_address,
282
+ "eth_signed_data" : types,
283
+ }
284
+
285
+ def eth_sign_text(self, message, signature_only=True):
286
+ """
287
+ Signs a text message using the private key.
288
+
289
+ Parameters
290
+ ----------
291
+ message : str
292
+ The message to sign.
293
+
294
+ signature_only : bool, optional
295
+ Whether to return only the signature. The default is True
296
+
297
+ Returns
298
+ -------
299
+ str
300
+ The signature of the message.
301
+ """
302
+ types = ["string"]
303
+ values = [message]
304
+ result = self.eth_sign_message(types, values)
305
+ if signature_only:
306
+ return result["signature"]
307
+ return result
308
+
309
+
310
+
311
+ def eth_sign_node_epochs(
312
+ self,
313
+ node,
314
+ epochs,
315
+ epochs_vals,
316
+ signature_only=True,
317
+ use_evm_node_addr=True
318
+ ):
319
+ """
320
+ Signs the node availability
321
+
322
+ Parameters
323
+ ----------
324
+ node : str
325
+ The node address to sign. Either the node address or the Ethereum address based on `use_evm_node_addr`.
326
+
327
+ epochs : list of int
328
+ The epochs to sign.
329
+
330
+ epochs_vals : list of int
331
+ The values for each epoch.
332
+
333
+ signature_only : bool, optional
334
+ Whether to return only the signature. The default is True.
335
+
336
+ use_evm_node_addr : bool, optional
337
+ Whether to use the Ethereum address of the node. The default is True.
338
+
339
+ Returns
340
+ -------
341
+ str
342
+ The signature of the message.
343
+ """
344
+ if use_evm_node_addr:
345
+ types = ["address", "uint256[]", "uint256[]"]
346
+ else:
347
+ types = ["string", "uint256[]", "uint256[]"]
348
+ values = [node, epochs, epochs_vals]
349
+ result = self.eth_sign_message(types, values)
350
+ if signature_only:
351
+ return result["signature"]
352
+ return result
353
+
354
+
355
+
@@ -15,6 +15,8 @@ class BCctbase:
15
15
  ETH_SENDER= 'EE_ETH_SENDER'
16
16
 
17
17
 
18
+ EE_VPN_IMPL_ENV_KEY = 'EE_VPN_IMPL'
19
+
18
20
  DAUTH_SUBKEY = 'auth'
19
21
  DAUTH_ENV_KEY = 'EE_DAUTH_URL'
20
22
  DAUTH_NONCE = 'nonce'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: naeural_client
3
- Version: 2.7.27
3
+ Version: 2.7.29
4
4
  Summary: `naeural_client` is the Python SDK required for client app development for the Naeural Edge Protocol Edge Protocol framework
5
5
  Project-URL: Homepage, https://github.com/NaeuralEdgeProtocol/naeural_client
6
6
  Project-URL: Bug Tracker, https://github.com/NaeuralEdgeProtocol/naeural_client/issues
@@ -1,5 +1,5 @@
1
1
  naeural_client/__init__.py,sha256=YimqgDbjLuywsf8zCWE0EaUXH4MBUrqLxt0TDV558hQ,632
2
- naeural_client/_ver.py,sha256=xPABtqoee7FOJbdkfLEdq81iAPs9A1AH0mTWt03WkkA,331
2
+ naeural_client/_ver.py,sha256=JqBpFIjrfwZRS_X2fLTG2ivMFcGrMcEOLgQ2jSW3iOY,331
3
3
  naeural_client/base_decentra_object.py,sha256=C4iwZTkhKNBS4VHlJs5DfElRYLo4Q9l1V1DNVSk1fyQ,4412
4
4
  naeural_client/plugins_manager_mixin.py,sha256=X1JdGLDz0gN1rPnTN_5mJXR8JmqoBFQISJXmPR9yvCo,11106
5
5
  naeural_client/base/__init__.py,sha256=hACh83_cIv7-PwYMM3bQm2IBmNqiHw-3PAfDfAEKz9A,259
@@ -14,9 +14,10 @@ naeural_client/base/webapp_pipeline.py,sha256=ZNGqZ36DY076XVDfGu2Q61kCt3kxIJ4Mi4
14
14
  naeural_client/base/payload/__init__.py,sha256=y8fBI8tG2ObNfaXFWjyWZXwu878FRYj_I8GIbHT4GKE,29
15
15
  naeural_client/base/payload/payload.py,sha256=x-au7l67Z_vfn_4R2C_pjZCaFuUVXHngJiGOfIAYVdE,2690
16
16
  naeural_client/bc/__init__.py,sha256=FQj23D1PrY06NUOARiKQi4cdj0-VxnoYgYDEht8lpr8,158
17
- naeural_client/bc/base.py,sha256=-BXy-o3mP3Z6Qo2oc0DbWAuUdT3wQOGJbED9QtQ2KCE,46624
17
+ naeural_client/bc/base.py,sha256=zhfzFFMYSmWB-2KLclvnEF35jobrNLFLIFc4ZvxCcE0,44568
18
18
  naeural_client/bc/chain.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- naeural_client/bc/ec.py,sha256=-HPfKpYAqy_eZpiJf3gWR2L4ZDDP7fZY2uwdNYYmS80,23868
19
+ naeural_client/bc/ec.py,sha256=FwlkWmJvQ9aHuf_BZX1CWSUAxw6OZ9jBparLIWcs_e4,18933
20
+ naeural_client/bc/evm.py,sha256=3QwHxU5nOM03OmcxtxaqCBaoft2o-QuAQ-hWD-miKJI,8937
20
21
  naeural_client/certs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
22
  naeural_client/certs/r9092118.ala.eu-central-1.emqxsl.com.crt,sha256=y-6io0tseyx9-a4Pmde1z1gPULtJNSYUpG_YFkYaMKU,1337
22
23
  naeural_client/certs/s624dbd4.ala.us-east-1.emqxsl.com.crt,sha256=y-6io0tseyx9-a4Pmde1z1gPULtJNSYUpG_YFkYaMKU,1337
@@ -34,7 +35,7 @@ naeural_client/comm/mqtt_wrapper.py,sha256=Ig3bFZkCbWd4y_Whn2PPa91Z3aLgNbNPau6Tn
34
35
  naeural_client/const/README.md,sha256=6OHesr-f5NBuuJGryEoi_TCu2XdlhfQYlDKx_IJoXeg,177
35
36
  naeural_client/const/__init__.py,sha256=MM6Zib6i7M2qWcMkLtLx14zqU-lE-u2uPHjNvbh2jAM,478
36
37
  naeural_client/const/apps.py,sha256=ePBiJXLuPfFOKuw-LJrT9OWbaodU7QApfDurIPNDoB4,655
37
- naeural_client/const/base.py,sha256=uq1HrxBsrFFiMP0yL0TcDANuxiSPafSx1Yh5ZyxRubE,5633
38
+ naeural_client/const/base.py,sha256=zfJyGGdrNz_OA5i_6cQxH2lEeJ4PO3092NrwM_gZ_U8,5670
38
39
  naeural_client/const/comms.py,sha256=La6JXWHexH8CfcBCKyT4fCIoeaoZlcm7KtZ57ab4ZgU,2201
39
40
  naeural_client/const/environment.py,sha256=RpdDhDgB8NgRoFTk28eODigf9y0WcT9lul6mBOD029w,879
40
41
  naeural_client/const/evm_net.py,sha256=mOSvsJnir1v_BrmKS51xIy9ddlZnQwlXWenzGXmXMOE,2606
@@ -85,8 +86,8 @@ naeural_client/utils/comm_utils.py,sha256=4cS9llRr_pK_3rNgDcRMCQwYPO0kcNU7AdWy_L
85
86
  naeural_client/utils/config.py,sha256=IG7-rRYBWCqCiI2kDesFmY8gld0Ku9xWgzNPE1rwVcA,10352
86
87
  naeural_client/utils/dotenv.py,sha256=_AgSo35n7EnQv5yDyu7C7i0kHragLJoCGydHjvOkrYY,2008
87
88
  naeural_client/utils/oracle_sync/oracle_tester.py,sha256=GmZwu2JM9_UB2K-4rKB3o0RgWLqM-7Im6HwBnQLXmHI,25312
88
- naeural_client-2.7.27.dist-info/METADATA,sha256=ljUYQkJp_UZZ1YFBTa40yuFvo-DgIAvTQUQJsVnmg50,12354
89
- naeural_client-2.7.27.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
90
- naeural_client-2.7.27.dist-info/entry_points.txt,sha256=CTua17GUrRa4aXeafezGC9TiWKGKQzwTjQmB2jyj22g,91
91
- naeural_client-2.7.27.dist-info/licenses/LICENSE,sha256=cvOsJVslde4oIaTCadabXnPqZmzcBO2f2zwXZRmJEbE,11311
92
- naeural_client-2.7.27.dist-info/RECORD,,
89
+ naeural_client-2.7.29.dist-info/METADATA,sha256=rWf_TRBs9LncFBWUHz9UAWuou8d5U3Y9vWXiXPN0410,12354
90
+ naeural_client-2.7.29.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
+ naeural_client-2.7.29.dist-info/entry_points.txt,sha256=CTua17GUrRa4aXeafezGC9TiWKGKQzwTjQmB2jyj22g,91
92
+ naeural_client-2.7.29.dist-info/licenses/LICENSE,sha256=cvOsJVslde4oIaTCadabXnPqZmzcBO2f2zwXZRmJEbE,11311
93
+ naeural_client-2.7.29.dist-info/RECORD,,