sgx.py 0.12.dev0__tar.gz → 0.12.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. sgx_py-0.12.dev2/PKG-INFO +38 -0
  2. sgx_py-0.12.dev2/README.md +112 -0
  3. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/setup.py +16 -8
  4. sgx_py-0.12.dev2/sgx/__init__.py +5 -0
  5. sgx_py-0.12.dev2/sgx/certificates.py +134 -0
  6. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/sgx/constants.py +1 -0
  7. sgx_py-0.12.dev2/sgx/generate.sh +18 -0
  8. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/sgx/http.py +66 -25
  9. sgx_py-0.12.dev2/sgx/options.py +104 -0
  10. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/sgx/sgx.py +23 -10
  11. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/sgx/sgx_rpc_handler.py +38 -11
  12. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/sgx/sgx_zmq.py +5 -9
  13. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/sgx/utils.py +27 -20
  14. sgx_py-0.12.dev2/sgx.py.egg-info/PKG-INFO +38 -0
  15. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/sgx.py.egg-info/SOURCES.txt +2 -0
  16. sgx_py-0.12.dev2/sgx.py.egg-info/requires.txt +23 -0
  17. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/tests/test_core.py +24 -8
  18. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/tests/test_dkg.py +10 -8
  19. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/tests/test_http.py +13 -2
  20. sgx_py-0.12.dev0/PKG-INFO +0 -30
  21. sgx_py-0.12.dev0/README.md +0 -75
  22. sgx_py-0.12.dev0/sgx/__init__.py +0 -2
  23. sgx_py-0.12.dev0/sgx/generate.sh +0 -1
  24. sgx_py-0.12.dev0/sgx.py.egg-info/PKG-INFO +0 -30
  25. sgx_py-0.12.dev0/sgx.py.egg-info/requires.txt +0 -15
  26. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/LICENSE +0 -0
  27. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/setup.cfg +0 -0
  28. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/sgx.py.egg-info/dependency_links.txt +0 -0
  29. {sgx_py-0.12.dev0 → sgx_py-0.12.dev2}/sgx.py.egg-info/top_level.txt +0 -0
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: sgx.py
3
+ Version: 0.12.dev2
4
+ Summary: SGX
5
+ Home-page: http://github.com/skalenetwork/sgx.py
6
+ Author: SKALE Labs
7
+ Author-email: support@skalelabs.com
8
+ Requires-Python: >=3.11,<4
9
+ License-File: LICENSE
10
+ Requires-Dist: cryptography<51,>=50.0.2
11
+ Requires-Dist: cytoolz>=0.12
12
+ Requires-Dist: eth-account<0.15,>=0.13
13
+ Requires-Dist: eth-hash[pycryptodome]>=0.7
14
+ Requires-Dist: eth-utils<7,>=5
15
+ Requires-Dist: hexbytes<3,>=1
16
+ Requires-Dist: pem==23.1.0
17
+ Requires-Dist: pyzmq==27.2.0
18
+ Requires-Dist: requests>=2.33.0
19
+ Requires-Dist: urllib3>=2.8.0
20
+ Provides-Extra: linter
21
+ Requires-Dist: ruff==0.17.2; extra == "linter"
22
+ Provides-Extra: dev
23
+ Requires-Dist: ruff==0.17.2; extra == "dev"
24
+ Requires-Dist: build==1.6.1; extra == "dev"
25
+ Requires-Dist: coincurve==13.0.0; extra == "dev"
26
+ Requires-Dist: python-dotenv==1.2.4; extra == "dev"
27
+ Requires-Dist: twine==7.0.0; extra == "dev"
28
+ Requires-Dist: pytest==9.1.1; extra == "dev"
29
+ Requires-Dist: mock==5.2.0; extra == "dev"
30
+ Requires-Dist: web3==8.0.0; extra == "dev"
31
+ Dynamic: author
32
+ Dynamic: author-email
33
+ Dynamic: home-page
34
+ Dynamic: license-file
35
+ Dynamic: provides-extra
36
+ Dynamic: requires-dist
37
+ Dynamic: requires-python
38
+ Dynamic: summary
@@ -0,0 +1,112 @@
1
+ # sgx.py
2
+ [![Discord](https://img.shields.io/discord/534485763354787851.svg)](https://discord.gg/vvUtWJB)
3
+
4
+ ## Requirements
5
+
6
+ |**Tool**|**Minimum Supported Version**|
7
+ |--------|-----------|
8
+ | Python | 3.11 |
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ### Install Python 3.11
15
+
16
+ ```bash
17
+ # confirm prerequisites are isntalled
18
+ sudo apt update
19
+ sudo apt install software-properties-common
20
+ # Add deadsnakes PPA - widely used for Python versions
21
+ sudo add-apt-repository ppa:deadsnakes/ppa
22
+ # update again & install python 3.11
23
+ sudo apt update
24
+ sudo apt install python3.11
25
+ # Check if it was installed
26
+ python3.11 --version
27
+
28
+ pip install sgx.py
29
+ ```
30
+ ---
31
+
32
+ ## Developers
33
+ ### Install all python libraries
34
+ ```bash
35
+ sudo apt-get update --fix-missing
36
+ sudo apt-get install libudev-dev
37
+ sudo apt-get install swig
38
+
39
+ # create virtual environment - install dependencies locally
40
+ python3.11 -m venv venv
41
+ source venv/bin/activate
42
+ pip install -e .
43
+ pip install -e .[dev]
44
+ ```
45
+
46
+ ### Running Tests
47
+
48
+ Create a `.env` file in source directory, like the following:
49
+ ```bash
50
+ # SGXWallet listening point (1026 uses https w/ certificates)
51
+ SERVER=https://127.0.0.1:1026
52
+ # path where you want tests to store the auto-generated certificates
53
+ CERT_PATH=.
54
+ # private key for the funded test account used by transaction tests
55
+ ETH_PRIVATE_KEY=
56
+ # Ethereum JSON-RPC endpoint used by transaction tests
57
+ GETH=
58
+ # 1 when sgxwallet runs with -e (ZMQ key ownership)
59
+ SGX_EXPECT_ZMQ_OWNERSHIP=0
60
+ ```
61
+ You may alter the fields as needed.
62
+
63
+ Run the tests:
64
+ ```bash
65
+ # Run all tests
66
+ pytest
67
+
68
+ # Run specific test file
69
+ pytest ./tests/<test-file-name>.py
70
+
71
+ ```
72
+
73
+ ## Checking the wallet
74
+
75
+ Available from sgxwallet 1.11.0. Older servers raise `SgxMethodNotFoundError`, a subclass of `SgxServerError`.
76
+
77
+ ### Client certificate
78
+
79
+ ```python
80
+ from sgx import CertificateMatch, SgxClient
81
+
82
+ client = SgxClient(endpoint, cert_dir, allow_registration=False)
83
+ check = client.check_local_certificate(expected_number=1)
84
+ if check.outcome is not CertificateMatch.MATCH:
85
+ print(check.outcome, check.newer_issued_at)
86
+ ```
87
+
88
+ | `check.outcome` | Meaning |
89
+ |---|---|
90
+ | `MATCH` | `sgx.crt` is the newest certificate the wallet issued, and the wallet issued `expected_number` client certificates |
91
+ | `UNEXPECTED_NUMBER` | `sgx.crt` is the newest, but the number of issued certificates differs |
92
+ | `NOT_LATEST` | the wallet issued a newer certificate, at `check.newer_issued_at` |
93
+ | `NOT_ISSUED` | unknown to the wallet's CA: wrong endpoint, or its CA database was recreated |
94
+
95
+ `check.info` holds both counts and the newest certificate with its status: `V`, `R` or `E`. The check relies on the server verifying client certificates; otherwise `NOT_LATEST` can also mean a certificate from another CA.
96
+
97
+ Unless `allow_registration=False` is passed, any request registers a new certificate when `sgx.crt` or `sgx.key` is missing, and the constructor makes such a request when the directory does not hold exactly three files. `check_local_certificate` never registers.
98
+
99
+ ### Server options
100
+
101
+ ```python
102
+ options = client.get_server_options()
103
+ options.effective.zmq_key_ownership_enforced # -e is in effect; applies to ZMQ only
104
+ ```
105
+
106
+ The server reports these values itself; they are not attested. `options.build` is `None` unless the server verifies the caller's certificate. While sgxwallet starts or stops, the call raises `SgxServerError`; retry.
107
+
108
+ ## License
109
+
110
+ [![License](https://img.shields.io/github/license/skalenetwork/sgx.py.svg)](LICENSE)
111
+
112
+ Copyright (C) 2019-present SKALE Labs
@@ -5,14 +5,16 @@ from setuptools import (
5
5
 
6
6
  extras_require = {
7
7
  'linter': [
8
- 'ruff==0.13.2',
8
+ 'ruff==0.17.2',
9
9
  ],
10
10
  'dev': [
11
+ 'build==1.6.1',
11
12
  'coincurve==13.0.0',
12
- 'python-dotenv==0.13.0',
13
- 'twine==6.2.0',
14
- 'pytest==8.4.2',
13
+ 'python-dotenv==1.2.4',
14
+ 'twine==7.0.0',
15
+ 'pytest==9.1.1',
15
16
  'mock==5.2.0',
17
+ 'web3==8.0.0',
16
18
  ],
17
19
  }
18
20
 
@@ -20,16 +22,22 @@ extras_require['dev'] = extras_require['linter'] + extras_require['dev']
20
22
 
21
23
  setup(
22
24
  name='sgx.py',
23
- version='0.12dev0',
25
+ version='0.12dev2',
24
26
  description='SGX',
25
27
  url='http://github.com/skalenetwork/sgx.py',
26
28
  author='SKALE Labs',
27
29
  author_email='support@skalelabs.com',
28
30
  install_requires=[
29
- 'web3>=6.20.2,<8.0.0',
30
- 'pyzmq==27.1.0',
31
+ 'cryptography>=50.0.2,<51',
32
+ 'cytoolz>=0.12',
33
+ 'eth-account>=0.13,<0.15',
34
+ 'eth-hash[pycryptodome]>=0.7',
35
+ 'eth-utils>=5,<7',
36
+ 'hexbytes>=1,<3',
31
37
  'pem==23.1.0',
32
- 'cryptography>=46.0.1,<47.0.0',
38
+ 'pyzmq==27.2.0',
39
+ 'requests>=2.33.0',
40
+ 'urllib3>=2.8.0',
33
41
  ],
34
42
  packages=find_packages(exclude=['tests']),
35
43
  python_requires='>=3.11,<4',
@@ -0,0 +1,5 @@
1
+ # flake8: noqa: F401
2
+ from sgx.certificates import CertificateMatch, IssuedCertificatesInfo, SgxCertificateError
3
+ from sgx.options import LogLevel, ServerOptions, SgxServerOptionsError
4
+ from sgx.sgx import SgxClient
5
+ from sgx.sgx_rpc_handler import SgxMethodNotFoundError
@@ -0,0 +1,134 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # This file is part of sgx.py
4
+ #
5
+ # Copyright (C) 2026 SKALE Labs
6
+ #
7
+ # sgx.py is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Affero General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # sgx.py is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Affero General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License
18
+ # along with sgx.py. If not, see <https://www.gnu.org/licenses/>.
19
+
20
+ import os
21
+ import re
22
+ from dataclasses import dataclass
23
+ from datetime import datetime, timezone
24
+ from enum import Enum
25
+
26
+ from cryptography import x509
27
+ from cryptography.hazmat.primitives import hashes
28
+
29
+ from sgx.constants import CRT_FILENAME
30
+ from sgx.utils import SgxError
31
+
32
+
33
+ class SgxCertificateError(SgxError):
34
+ pass
35
+
36
+
37
+ class CertificateMatch(Enum):
38
+ MATCH = 'match' # the local certificate is the newest issued
39
+ UNEXPECTED_NUMBER = 'unexpected_number' # as MATCH, but the count differs from the expected
40
+ NOT_LATEST = 'not_latest' # a newer certificate was issued after it
41
+ NOT_ISSUED = 'not_issued' # unknown to this wallet's CA database
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class NewestCertificate:
46
+ serial: int
47
+ sha256: bytes
48
+ not_before: datetime
49
+ not_after: datetime
50
+ status: str # V valid, R revoked, E expired
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class IssuedCertificatesInfo:
55
+ """Client certificates issued by the wallet's CA, from getIssuedCertificatesInfo."""
56
+
57
+ certificates_number: int
58
+ server_certificates_number: int
59
+ newest_certificate: NewestCertificate | None
60
+
61
+ @classmethod
62
+ def from_result(cls, result):
63
+ """Parses a result in the format sgxwallet documents and rejects anything else."""
64
+ try:
65
+ number = _count(result['certificatesNumber'])
66
+ newest = result['newestCertificate']
67
+ # sgxwallet sends a newest certificate exactly when it has issued any
68
+ if (newest is None) != (number == 0):
69
+ raise ValueError('newestCertificate does not match certificatesNumber')
70
+ if newest is not None:
71
+ newest = NewestCertificate(
72
+ serial=int(_text('[0-9A-F]+', newest['serial']), 16),
73
+ sha256=bytes.fromhex(_text('[0-9a-f]{64}', newest['sha256'])),
74
+ not_before=_utc(newest['notBefore']),
75
+ not_after=_utc(newest['notAfter']),
76
+ status=_text('[VRE]', newest['status']),
77
+ )
78
+ return cls(number, _count(result['serverCertificatesNumber']), newest)
79
+ except (KeyError, TypeError, ValueError) as err:
80
+ raise SgxCertificateError(
81
+ f'Malformed getIssuedCertificatesInfo result: {result!r}'
82
+ ) from err
83
+
84
+ def check(self, certificate, expected_number=None):
85
+ """Compares an x509.Certificate with the newest certificate the wallet issued. If the
86
+ server does not verify client certificates, NOT_LATEST may be another CA's certificate."""
87
+ newest = self.newest_certificate
88
+ if newest is not None and newest.sha256 == certificate.fingerprint(hashes.SHA256()):
89
+ if expected_number is None or expected_number == self.certificates_number:
90
+ outcome = CertificateMatch.MATCH
91
+ else:
92
+ outcome = CertificateMatch.UNEXPECTED_NUMBER
93
+ elif newest is not None and newest.serial > certificate.serial_number:
94
+ outcome = CertificateMatch.NOT_LATEST
95
+ else:
96
+ outcome = CertificateMatch.NOT_ISSUED
97
+ return CertificateCheck(outcome, self)
98
+
99
+
100
+ @dataclass(frozen=True)
101
+ class CertificateCheck:
102
+ outcome: CertificateMatch
103
+ info: IssuedCertificatesInfo
104
+
105
+ @property
106
+ def newer_issued_at(self):
107
+ if self.outcome is CertificateMatch.NOT_LATEST:
108
+ return self.info.newest_certificate.not_before
109
+ return None
110
+
111
+
112
+ def load_local_certificate(cert_dir):
113
+ try:
114
+ with open(os.path.join(cert_dir, CRT_FILENAME), 'rb') as crt_file:
115
+ return x509.load_pem_x509_certificate(crt_file.read())
116
+ except (OSError, TypeError, ValueError) as err: # TypeError: no cert_dir
117
+ raise SgxCertificateError(f'Cannot load {CRT_FILENAME} from {cert_dir!r}') from err
118
+
119
+
120
+ def _count(value):
121
+ # Exact type, because bool is a subclass of int
122
+ if type(value) is not int or value < 0:
123
+ raise ValueError(f'Invalid count {value!r}')
124
+ return value
125
+
126
+
127
+ def _text(pattern, value):
128
+ if not re.fullmatch(pattern, value):
129
+ raise ValueError(f'Invalid value {value!r}')
130
+ return value
131
+
132
+
133
+ def _utc(value):
134
+ return datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone.utc)
@@ -10,3 +10,4 @@ SGX_ZMQ_RESPONSE_TIMEOUT_MS = 5000
10
10
  CSR_FILENAME = 'sgx.csr'
11
11
  KEY_FILENAME = 'sgx.key'
12
12
  CRT_FILENAME = 'sgx.crt'
13
+ LOCK_FILENAME = 'sgx.enroll.lock'
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+ umask 077
5
+
6
+ CSR_PATH="$1"
7
+ KEY_PATH="$2"
8
+ COMMON_NAME="$3"
9
+
10
+ rm -f -- "$CSR_PATH" "$KEY_PATH"
11
+
12
+ openssl req -new -sha256 -nodes \
13
+ -out "$CSR_PATH" \
14
+ -newkey rsa:2048 \
15
+ -keyout "$KEY_PATH" \
16
+ -subj "/CN=$COMMON_NAME"
17
+
18
+ chmod 600 "$KEY_PATH"
@@ -1,29 +1,34 @@
1
- import os
1
+ import fcntl
2
+ import json
2
3
  import logging
4
+ import os
3
5
  import secrets
4
- import requests
5
- import json
6
+ from contextlib import contextmanager
6
7
  from time import sleep
7
8
  from urllib.parse import urlparse
8
9
 
10
+ import requests
9
11
  import urllib3
10
12
  from requests.packages.urllib3.exceptions import InsecureRequestWarning
13
+
14
+ from sgx.certificates import SgxCertificateError
11
15
  from sgx.constants import (
12
- GENERATE_SCRIPT_PATH,
13
- DEFAULT_TIMEOUT,
14
- CSR_FILENAME,
15
16
  CRT_FILENAME,
17
+ CSR_FILENAME,
18
+ DEFAULT_TIMEOUT,
19
+ GENERATE_SCRIPT_PATH,
16
20
  KEY_FILENAME,
17
- SGX_RESPONSE_TIMEOUT
21
+ LOCK_FILENAME,
22
+ SGX_RESPONSE_TIMEOUT,
18
23
  )
19
- from sgx.utils import run_cmd, print_request_log, print_response_log, SgxError
20
-
24
+ from sgx.utils import SgxError, print_request_log, print_response_log, run_cmd
21
25
 
22
26
  requests.packages.urllib3.disable_warnings(InsecureRequestWarning) # TODO: Remove
23
27
  logger = logging.getLogger(__name__)
24
28
 
25
29
 
26
30
  MAX_RETRIES = 22
31
+ CSR_STATUS_PENDING = 1
27
32
 
28
33
 
29
34
  class SgxSSLError(SgxError):
@@ -34,15 +39,40 @@ class SgxUnreachableError(SgxError):
34
39
  pass
35
40
 
36
41
 
37
- def get_certificate_credentials(crt_dir_path, csr_server):
42
+ def certificate_exists(crt_dir_path: str) -> bool:
43
+ return all(
44
+ os.path.exists(os.path.join(crt_dir_path, filename))
45
+ for filename in (CRT_FILENAME, KEY_FILENAME)
46
+ )
47
+
48
+
49
+ @contextmanager
50
+ def enrollment_lock(crt_dir_path: str):
51
+ lock_path = os.path.join(crt_dir_path, LOCK_FILENAME)
52
+ fd = os.open(lock_path, os.O_CREAT | os.O_RDWR, 0o600)
53
+ try:
54
+ fcntl.flock(fd, fcntl.LOCK_EX)
55
+ yield
56
+ finally:
57
+ os.close(fd)
58
+
59
+
60
+ def get_certificate_credentials(crt_dir_path, csr_server, allow_registration=True):
38
61
  key_path = os.path.join(crt_dir_path, KEY_FILENAME)
39
62
  crt_path = os.path.join(crt_dir_path, CRT_FILENAME)
40
- if not os.path.exists(crt_path) or not os.path.exists(key_path):
41
- csr_path = os.path.join(crt_dir_path, CSR_FILENAME)
42
- if not os.path.exists(csr_path) or not os.path.exists(key_path):
43
- generate_csr_credentials(csr_path, key_path)
44
- csr_hash = sign_certificate(csr_server, csr_path)
45
- write_crt_to_file(crt_path, csr_server, csr_hash)
63
+ if certificate_exists(crt_dir_path):
64
+ return crt_path, key_path
65
+ if not allow_registration:
66
+ raise SgxCertificateError(
67
+ f'{crt_path} or {key_path} is missing and registration is disabled'
68
+ )
69
+ with enrollment_lock(crt_dir_path):
70
+ if not certificate_exists(crt_dir_path):
71
+ csr_path = os.path.join(crt_dir_path, CSR_FILENAME)
72
+ if not os.path.exists(csr_path) or not os.path.exists(key_path):
73
+ generate_csr_credentials(csr_path, key_path)
74
+ csr_hash = sign_certificate(csr_server, csr_path)
75
+ write_crt_to_file(crt_path, csr_server, csr_hash)
46
76
  return crt_path, key_path
47
77
 
48
78
 
@@ -52,13 +82,23 @@ def generate_csr_credentials(csr_path, key_path):
52
82
 
53
83
 
54
84
  def write_crt_to_file(crt_path, csr_server, csr_hash):
55
- response = send_request_safe(csr_server, 'getCertificate', {'hash': csr_hash})
56
- while response['result']['status'] == 1:
57
- response = send_request_safe(csr_server, 'getCertificate', {'hash': csr_hash})
58
- sleep(DEFAULT_TIMEOUT)
59
- crt = response['result']['cert']
60
- with open(crt_path, "w+") as f:
61
- f.write(crt)
85
+ for _ in range(MAX_RETRIES):
86
+ response = send_request(csr_server, 'getCertificate', {'hash': csr_hash})
87
+ if response.get('error'):
88
+ raise SgxSSLError(response['error']['message'])
89
+ result = response['result']
90
+ if result['status'] == CSR_STATUS_PENDING:
91
+ sleep(DEFAULT_TIMEOUT)
92
+ continue
93
+ if result['status'] != 0:
94
+ raise SgxSSLError(result['errorMessage'])
95
+ with open(crt_path, 'w') as f:
96
+ f.write(result['cert'])
97
+ return
98
+ raise SgxSSLError(
99
+ f'Certificate signing request {csr_hash} is still pending '
100
+ f'after {MAX_RETRIES} attempts'
101
+ )
62
102
 
63
103
 
64
104
  def sign_certificate(csr_server, csr_path):
@@ -69,7 +109,7 @@ def sign_certificate(csr_server, csr_path):
69
109
  return csr_hash
70
110
 
71
111
 
72
- def send_request(url, method, params, path_to_cert=None):
112
+ def send_request(url, method, params, path_to_cert=None, allow_registration=True):
73
113
  headers = {'content-type': 'application/json'}
74
114
  call_data = {
75
115
  "id": 0,
@@ -82,7 +122,8 @@ def send_request(url, method, params, path_to_cert=None):
82
122
  if path_to_cert:
83
123
  cert = get_certificate_credentials(
84
124
  path_to_cert,
85
- get_cert_provider(url)
125
+ get_cert_provider(url),
126
+ allow_registration
86
127
  )
87
128
 
88
129
  try:
@@ -0,0 +1,104 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # This file is part of sgx.py
4
+ #
5
+ # Copyright (C) 2026 SKALE Labs
6
+ #
7
+ # sgx.py is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Affero General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # sgx.py is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Affero General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License
18
+ # along with sgx.py. If not, see <https://www.gnu.org/licenses/>.
19
+
20
+ from dataclasses import dataclass, fields
21
+ from enum import IntEnum
22
+
23
+ from sgx.utils import SgxError
24
+
25
+
26
+ class SgxServerOptionsError(SgxError):
27
+ pass
28
+
29
+
30
+ class LogLevel(IntEnum):
31
+ TRACE = 0
32
+ DEBUG = 1
33
+ INFO = 2
34
+ WARNING = 3
35
+ ERROR = 4
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class ServerFlags:
40
+ log_level: LogLevel
41
+ enclave_log_level: LogLevel
42
+ use_https: bool
43
+ autoconfirm: bool
44
+ enter_backup_key: bool
45
+ reencrypt_database_with_new_sek: bool
46
+ check_cert: bool
47
+ check_zmq_sig: bool
48
+ auto_sign: bool
49
+ generate_test_keys: bool
50
+ check_key_ownership: bool
51
+ thread_pool_size: int
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class EffectiveOptions:
56
+ rpc_port: int # as bound inside the server, before any port mapping
57
+ rpc_client_certificate_required: bool
58
+ zmq_key_ownership_enforced: bool # -e is in effect; applies to ZMQ only
59
+ json_rpc_key_ownership_enforced: bool
60
+ sgx_thread_pool_size: int
61
+
62
+
63
+ @dataclass(frozen=True)
64
+ class BuildInfo:
65
+ sgx_simulation: bool
66
+ sgx_debug_launch: bool
67
+
68
+
69
+ @dataclass(frozen=True)
70
+ class ServerOptions:
71
+ """Options sgxwallet reports about itself, from getServerOptions; they are not attested.
72
+ build is sent only to authenticated callers: HTTPS with the client certificate check."""
73
+
74
+ flags: ServerFlags
75
+ effective: EffectiveOptions
76
+ build: BuildInfo | None = None
77
+
78
+ @classmethod
79
+ def from_result(cls, result):
80
+ if not isinstance(result, dict):
81
+ raise SgxServerOptionsError(f'Malformed getServerOptions result: {result!r}')
82
+ build = result.get('build')
83
+ return cls(
84
+ _load(ServerFlags, result.get('flags')),
85
+ _load(EffectiveOptions, result.get('effective')),
86
+ None if build is None else _load(BuildInfo, build),
87
+ )
88
+
89
+
90
+ def _load(cls, data):
91
+ # Fields match the camelCase keys ignoring case and underscores: use_https is useHTTPS
92
+ try:
93
+ values = {key.lower(): value for key, value in data.items()}
94
+ kwargs = {f.name: _typed(f.type, values[f.name.replace('_', '')]) for f in fields(cls)}
95
+ except (AttributeError, KeyError, TypeError, ValueError) as err:
96
+ raise SgxServerOptionsError(f'Malformed {cls.__name__}: {data!r}') from err
97
+ return cls(**kwargs)
98
+
99
+
100
+ def _typed(kind, value):
101
+ # Exact types, because bool is a subclass of int
102
+ if type(value) is not (int if kind is LogLevel else kind):
103
+ raise TypeError(f'{value!r} is not {kind.__name__}')
104
+ return kind(value)
@@ -18,20 +18,22 @@
18
18
  # along with sgx.py. If not, see <https://www.gnu.org/licenses/>.
19
19
 
20
20
  import logging
21
- from dataclasses import dataclass
22
21
  from collections.abc import Mapping
23
- from hexbytes import HexBytes
24
- from eth_account.datastructures import SignedTransaction, SignedMessage
25
- from eth_utils.curried import keccak
22
+ from dataclasses import dataclass
23
+
26
24
  from cytoolz import dissoc
27
- from sgx.sgx_rpc_handler import SgxRPCHandler
28
- from sgx.sgx_zmq import SgxZmq
29
- from sgx.utils import public_key_to_address
30
25
  from eth_account._utils import signing
31
26
  from eth_account._utils.legacy_transactions import encode_transaction
27
+ from eth_account.datastructures import SignedMessage, SignedTransaction
32
28
  from eth_account.typed_transactions.typed_transaction import TypedTransaction
33
- from eth_utils.encoding import big_endian_to_int
34
29
  from eth_utils.conversions import add_0x_prefix, remove_0x_prefix
30
+ from eth_utils.curried import keccak
31
+ from eth_utils.encoding import big_endian_to_int
32
+ from hexbytes import HexBytes
33
+
34
+ from sgx.sgx_rpc_handler import SgxRPCHandler
35
+ from sgx.sgx_zmq import SgxZmq
36
+ from sgx.utils import public_key_to_address
35
37
 
36
38
  logger = logging.getLogger(__name__)
37
39
 
@@ -51,9 +53,11 @@ class ComplaintResponse:
51
53
 
52
54
 
53
55
  class SgxClient:
54
- def __init__(self, sgx_endpoint, path_to_cert=None, n=None, t=None, zmq=False):
56
+ def __init__(
57
+ self, sgx_endpoint, path_to_cert=None, n=None, t=None, zmq=False, allow_registration=True
58
+ ):
55
59
  self.sgx_endpoint = sgx_endpoint
56
- self.sgx_rpc_server = SgxRPCHandler(sgx_endpoint, path_to_cert)
60
+ self.sgx_rpc_server = SgxRPCHandler(sgx_endpoint, path_to_cert, allow_registration)
57
61
  if zmq:
58
62
  self.zmq = SgxZmq(sgx_endpoint, path_to_cert, n, t)
59
63
  if not path_to_cert:
@@ -149,6 +153,15 @@ class SgxClient:
149
153
  def get_server_version(self):
150
154
  return self.sgx_rpc_server.get_server_version()
151
155
 
156
+ def get_server_options(self):
157
+ return self.sgx_rpc_server.get_server_options()
158
+
159
+ def get_issued_certificates_info(self):
160
+ return self.sgx_rpc_server.get_issued_certificates_info()
161
+
162
+ def check_local_certificate(self, expected_number=None):
163
+ return self.sgx_rpc_server.check_local_certificate(expected_number)
164
+
152
165
  def verify_secret_share(self, public_shares, eth_key_name, secret_share, index):
153
166
  return self.sgx_rpc_server.verify_secret_share(
154
167
  public_shares, eth_key_name, secret_share, self.n, self.t, index
@@ -18,22 +18,27 @@
18
18
  # along with sgx.py. If not, see <https://www.gnu.org/licenses/>.
19
19
 
20
20
  import logging
21
- import os
22
21
  from enum import Enum
23
-
24
22
  from urllib.parse import urlparse
25
23
 
26
- from sgx.http import send_request
24
+ from sgx.certificates import IssuedCertificatesInfo, load_local_certificate
25
+ from sgx.http import certificate_exists, send_request
26
+ from sgx.options import ServerOptions
27
27
  from sgx.utils import SgxError
28
28
 
29
-
30
29
  logger = logging.getLogger(__name__)
31
30
 
31
+ METHOD_NOT_FOUND = -32601
32
+
32
33
 
33
34
  class SgxServerError(SgxError):
34
35
  pass
35
36
 
36
37
 
38
+ class SgxMethodNotFoundError(SgxServerError):
39
+ """The server does not implement the method, e.g. an sgxwallet older than 1.10.4."""
40
+
41
+
37
42
  class DkgPolyStatus(Enum):
38
43
  FAIL = 0
39
44
  NEW_GENERATED = 1
@@ -41,10 +46,11 @@ class DkgPolyStatus(Enum):
41
46
 
42
47
 
43
48
  class SgxRPCHandler:
44
- def __init__(self, sgx_endpoint, path_to_cert):
49
+ def __init__(self, sgx_endpoint, path_to_cert, allow_registration=True):
45
50
  self.sgx_endpoint = check_provider(sgx_endpoint)
46
51
  self.path_to_cert = path_to_cert
47
- if path_to_cert and len(os.listdir(path_to_cert)) != 3:
52
+ self.allow_registration = allow_registration
53
+ if allow_registration and path_to_cert and not certificate_exists(path_to_cert):
48
54
  self.get_server_status()
49
55
 
50
56
  def ecdsa_sign(self, key_name, transaction_hash):
@@ -133,6 +139,21 @@ class SgxRPCHandler:
133
139
  response = self.__send_request("getServerVersion")
134
140
  return response['result']['version']
135
141
 
142
+ def get_server_options(self):
143
+ response = self.__send_request('getServerOptions')
144
+ return ServerOptions.from_result(response['result'])
145
+
146
+ def get_issued_certificates_info(self):
147
+ response = self.__send_request('getIssuedCertificatesInfo')
148
+ return IssuedCertificatesInfo.from_result(response['result'])
149
+
150
+ def check_local_certificate(self, expected_number=None):
151
+ certificate = load_local_certificate(self.path_to_cert)
152
+ # Never registers: a new certificate would replace the one being checked
153
+ response = self.__send_request('getIssuedCertificatesInfo', allow_registration=False)
154
+ info = IssuedCertificatesInfo.from_result(response['result'])
155
+ return info.check(certificate, expected_number)
156
+
136
157
  def verify_secret_share(self, public_shares, eth_key_name, secret_share, n, t, index):
137
158
  params = dict()
138
159
  params['publicShares'] = public_shares
@@ -179,7 +200,9 @@ class SgxRPCHandler:
179
200
  response = self.__send_request("createBLSPrivateKeyV2", params)
180
201
  return response['result']['status'] == 0
181
202
 
182
- def create_bls_private_key_v3(self, poly_name, bls_key_name, eth_key_name, secret_contributions, n, t):
203
+ def create_bls_private_key_v3(
204
+ self, poly_name, bls_key_name, eth_key_name, secret_contributions, n, t
205
+ ):
183
206
  params = dict()
184
207
  # polyName is optional in V3 - new nodes joining the network do not pass it
185
208
  if poly_name:
@@ -268,10 +291,14 @@ class SgxRPCHandler:
268
291
 
269
292
  return result
270
293
 
271
- def __send_request(self, method, params=None):
272
- response = send_request(self.sgx_endpoint, method, params, self.path_to_cert)
273
- if response.get('error') is not None:
274
- raise SgxServerError(response['error']['message'])
294
+ def __send_request(self, method, params=None, allow_registration=True):
295
+ registration = self.allow_registration and allow_registration
296
+ response = send_request(self.sgx_endpoint, method, params, self.path_to_cert, registration)
297
+ error = response.get('error')
298
+ if error is not None:
299
+ if error.get('code') == METHOD_NOT_FOUND:
300
+ raise SgxMethodNotFoundError(error['message'])
301
+ raise SgxServerError(error['message'])
275
302
  if response['result']['status']:
276
303
  raise SgxServerError(response['result']['errorMessage'])
277
304
  return response
@@ -24,20 +24,17 @@ import os
24
24
  from dataclasses import dataclass
25
25
  from enum import Enum
26
26
  from time import sleep
27
- from sgx.utils import public_key_to_address
27
+ from urllib.parse import urlparse
28
28
 
29
- from eth_utils.conversions import add_0x_prefix, remove_0x_prefix
30
29
  import pem
31
30
  import zmq
32
31
  from cryptography.hazmat.primitives import hashes
33
- from cryptography.hazmat.primitives.asymmetric import ec, rsa, padding
32
+ from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa
34
33
  from cryptography.hazmat.primitives.serialization import load_pem_private_key
34
+ from eth_utils.conversions import add_0x_prefix, remove_0x_prefix
35
35
 
36
- from urllib.parse import urlparse
37
-
38
- from sgx.constants import DEFAULT_TIMEOUT, SGX_ZMQ_RESPONSE_TIMEOUT_MS, CRT_FILENAME, KEY_FILENAME
39
- from sgx.utils import SgxError
40
-
36
+ from sgx.constants import CRT_FILENAME, DEFAULT_TIMEOUT, KEY_FILENAME, SGX_ZMQ_RESPONSE_TIMEOUT_MS
37
+ from sgx.utils import SgxError, public_key_to_address
41
38
 
42
39
  logger = logging.getLogger(__name__)
43
40
 
@@ -291,7 +288,6 @@ class SgxZmq:
291
288
  p_id = os.getpid()
292
289
  if not self.sockets.get(p_id):
293
290
  socket_p_id = self.ctx.socket(zmq.DEALER)
294
- socket_p_id.setsockopt_string(zmq.IDENTITY, '135:14603077656239261618')
295
291
  socket_p_id.setsockopt(zmq.LINGER, 0)
296
292
  socket_p_id.setsockopt(zmq.SNDTIMEO, SGX_ZMQ_RESPONSE_TIMEOUT_MS)
297
293
  socket_p_id.setsockopt(zmq.RCVTIMEO, SGX_ZMQ_RESPONSE_TIMEOUT_MS)
@@ -18,48 +18,55 @@
18
18
  # along with sgx.py. If not, see <https://www.gnu.org/licenses/>.
19
19
 
20
20
 
21
- import copy
22
21
  import logging
23
22
  import os
24
23
  import subprocess
25
24
  from subprocess import PIPE
26
25
 
27
- from web3 import Web3
28
-
26
+ from eth_utils import keccak, to_checksum_address, to_hex
29
27
 
30
28
  logger = logging.getLogger(__name__)
31
29
 
30
+ REDACTED = '<redacted>'
31
+ SECRET_FIELDS = frozenset({
32
+ 'dhKey',
33
+ 'key',
34
+ 'keyShare',
35
+ 'secretContributions',
36
+ 'secretShare',
37
+ })
38
+
32
39
 
33
40
  class SgxError(Exception):
34
41
  pass
35
42
 
36
43
 
37
- def crop_json(json_data, crop_len=50):
38
- for key, value in json_data.items():
39
- if isinstance(value, dict):
40
- crop_json(value)
41
- else:
42
- if isinstance(value, str) and len(value) > crop_len:
43
- json_data[key] = value[:crop_len] + '...'
44
+ def redact(data: object, crop_len: int = 50) -> object:
45
+ if isinstance(data, dict):
46
+ return {
47
+ key: REDACTED if key in SECRET_FIELDS else redact(value, crop_len)
48
+ for key, value in data.items()
49
+ }
50
+ if isinstance(data, list):
51
+ return [redact(item, crop_len) for item in data]
52
+ if isinstance(data, str) and len(data) > crop_len:
53
+ return data[:crop_len] + '...'
54
+ return data
44
55
 
45
56
 
46
57
  def print_request_log(request):
47
- cropped_request = copy.deepcopy(request)
48
- crop_json(cropped_request)
49
- logger.info(f'Send request: {request}')
58
+ logger.info(f'Send request: {redact(request)}')
50
59
 
51
60
 
52
61
  def print_response_log(response):
53
- cropped_response = copy.deepcopy(response)
54
- crop_json(cropped_response)
55
- logger.info(f'Response received: {cropped_response}')
62
+ logger.info(f'Response received: {redact(response)}')
56
63
 
57
64
 
58
- def run_cmd(cmd, env={}, shell=False):
65
+ def run_cmd(cmd, env=None, shell=False):
59
66
  logger.info(f'Running: {cmd}')
60
67
  res = subprocess.run(
61
68
  cmd, shell=shell, stdout=PIPE, stderr=PIPE,
62
- env={**env, **os.environ}
69
+ env={**os.environ, **(env or {})}
63
70
  )
64
71
  if res.returncode:
65
72
  logger.error('Error during shell execution:')
@@ -69,5 +76,5 @@ def run_cmd(cmd, env={}, shell=False):
69
76
 
70
77
 
71
78
  def public_key_to_address(pk):
72
- hash_ = Web3.keccak(hexstr=str(pk))
73
- return Web3.to_checksum_address(Web3.to_hex(hash_[-20:]))
79
+ hash_ = keccak(hexstr=str(pk))
80
+ return to_checksum_address(to_hex(hash_[-20:]))
@@ -0,0 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: sgx.py
3
+ Version: 0.12.dev2
4
+ Summary: SGX
5
+ Home-page: http://github.com/skalenetwork/sgx.py
6
+ Author: SKALE Labs
7
+ Author-email: support@skalelabs.com
8
+ Requires-Python: >=3.11,<4
9
+ License-File: LICENSE
10
+ Requires-Dist: cryptography<51,>=50.0.2
11
+ Requires-Dist: cytoolz>=0.12
12
+ Requires-Dist: eth-account<0.15,>=0.13
13
+ Requires-Dist: eth-hash[pycryptodome]>=0.7
14
+ Requires-Dist: eth-utils<7,>=5
15
+ Requires-Dist: hexbytes<3,>=1
16
+ Requires-Dist: pem==23.1.0
17
+ Requires-Dist: pyzmq==27.2.0
18
+ Requires-Dist: requests>=2.33.0
19
+ Requires-Dist: urllib3>=2.8.0
20
+ Provides-Extra: linter
21
+ Requires-Dist: ruff==0.17.2; extra == "linter"
22
+ Provides-Extra: dev
23
+ Requires-Dist: ruff==0.17.2; extra == "dev"
24
+ Requires-Dist: build==1.6.1; extra == "dev"
25
+ Requires-Dist: coincurve==13.0.0; extra == "dev"
26
+ Requires-Dist: python-dotenv==1.2.4; extra == "dev"
27
+ Requires-Dist: twine==7.0.0; extra == "dev"
28
+ Requires-Dist: pytest==9.1.1; extra == "dev"
29
+ Requires-Dist: mock==5.2.0; extra == "dev"
30
+ Requires-Dist: web3==8.0.0; extra == "dev"
31
+ Dynamic: author
32
+ Dynamic: author-email
33
+ Dynamic: home-page
34
+ Dynamic: license-file
35
+ Dynamic: provides-extra
36
+ Dynamic: requires-dist
37
+ Dynamic: requires-python
38
+ Dynamic: summary
@@ -2,9 +2,11 @@ LICENSE
2
2
  README.md
3
3
  setup.py
4
4
  sgx/__init__.py
5
+ sgx/certificates.py
5
6
  sgx/constants.py
6
7
  sgx/generate.sh
7
8
  sgx/http.py
9
+ sgx/options.py
8
10
  sgx/sgx.py
9
11
  sgx/sgx_rpc_handler.py
10
12
  sgx/sgx_zmq.py
@@ -0,0 +1,23 @@
1
+ cryptography<51,>=50.0.2
2
+ cytoolz>=0.12
3
+ eth-account<0.15,>=0.13
4
+ eth-hash[pycryptodome]>=0.7
5
+ eth-utils<7,>=5
6
+ hexbytes<3,>=1
7
+ pem==23.1.0
8
+ pyzmq==27.2.0
9
+ requests>=2.33.0
10
+ urllib3>=2.8.0
11
+
12
+ [dev]
13
+ ruff==0.17.2
14
+ build==1.6.1
15
+ coincurve==13.0.0
16
+ python-dotenv==1.2.4
17
+ twine==7.0.0
18
+ pytest==9.1.1
19
+ mock==5.2.0
20
+ web3==8.0.0
21
+
22
+ [linter]
23
+ ruff==0.17.2
@@ -1,27 +1,24 @@
1
- import urllib
2
1
  import os
3
2
  import secrets
3
+ import socket
4
+ import urllib
4
5
 
5
6
  import eth_account._utils.legacy_transactions as transactions
6
7
  import pytest
7
-
8
- from eth_account.messages import defunct_hash_message, encode_defunct
9
-
10
8
  from dotenv import load_dotenv
9
+ from eth_account.messages import defunct_hash_message, encode_defunct
11
10
  from eth_keys import keys
12
11
  from hexbytes import HexBytes
13
- import socket
14
12
  from web3 import Web3
15
13
 
16
-
17
- from sgx import SgxClient
18
-
14
+ from sgx import CertificateMatch, SgxClient
19
15
 
20
16
  load_dotenv()
21
17
 
22
18
  SGX_URL = os.getenv('SERVER')
23
19
  GETH_URL = os.getenv('GETH')
24
20
  ETH_PRIVATE_KEY = os.getenv('ETH_PRIVATE_KEY')
21
+ EXPECT_ZMQ_OWNERSHIP = os.getenv('SGX_EXPECT_ZMQ_OWNERSHIP') == '1'
25
22
 
26
23
  ETH_VALUE_FOR_TESTS = 5 * 10**18
27
24
 
@@ -151,6 +148,25 @@ def test_get_server_version(sgx):
151
148
  assert isinstance(sgx.get_server_version(), str)
152
149
 
153
150
 
151
+ def test_get_server_options(sgx):
152
+ url = urllib.parse.urlparse(SGX_URL)
153
+ options = sgx.get_server_options()
154
+ assert options.effective.rpc_port == url.port
155
+ assert options.flags.use_https == (url.scheme == 'https')
156
+ assert options.flags.auto_sign and options.flags.autoconfirm
157
+ assert options.effective.zmq_key_ownership_enforced == EXPECT_ZMQ_OWNERSHIP
158
+ assert (options.build is not None) == options.effective.rpc_client_certificate_required
159
+ assert options.build is None or options.build.sgx_simulation
160
+
161
+
162
+ def test_check_local_certificate(sgx):
163
+ # A fresh simulator, and every test shares CERT_PATH: one client certificate
164
+ check = sgx.check_local_certificate(expected_number=1)
165
+ assert check.outcome is CertificateMatch.MATCH
166
+ assert check.info.server_certificates_number == 1
167
+ assert check.info.newest_certificate.status == 'V'
168
+
169
+
154
170
  def test_sign_message(sgx, account, w3):
155
171
  key, address = account
156
172
  txn = generate_tx(w3, tx_type=None, no_fee=True)
@@ -1,14 +1,16 @@
1
- from sgx import SgxClient
2
- from sgx.sgx_rpc_handler import SgxServerError
3
- from sgx.sgx_zmq import SgxZmqServerError
1
+ import binascii
2
+ import hashlib
4
3
  import os
5
- from time import sleep
6
- from dotenv import load_dotenv
7
4
  import random
8
- import coincurve
9
- import binascii
10
5
  import secrets
11
- import hashlib
6
+ from time import sleep
7
+
8
+ import coincurve
9
+ from dotenv import load_dotenv
10
+
11
+ from sgx import SgxClient
12
+ from sgx.sgx_rpc_handler import SgxServerError
13
+ from sgx.sgx_zmq import SgxZmqServerError
12
14
 
13
15
  load_dotenv()
14
16
 
@@ -1,10 +1,11 @@
1
+ import logging
2
+
1
3
  import mock
2
4
  import pytest
3
5
  import requests
4
6
  import urllib3
5
7
 
6
- from sgx.http import send_request
7
- from sgx.http import SgxUnreachableError
8
+ from sgx.http import SgxUnreachableError, send_request
8
9
 
9
10
  URL = 'http://127.0.0.1:1026'
10
11
  INVALID_URL = 'http://127.0.0.1:1033'
@@ -32,3 +33,13 @@ def test_send_request_failed_sgx_up():
32
33
  with mock.patch('requests.post', post_mock):
33
34
  with pytest.raises(SgxUnreachableError):
34
35
  send_request(URL, method, params)
36
+
37
+
38
+ def test_send_request_logs_redacted_params(caplog):
39
+ response = mock.Mock(**{'json.return_value': {'result': {'status': 0}}})
40
+ with mock.patch('requests.post', return_value=response), caplog.at_level(logging.INFO):
41
+ send_request(URL, 'importBLSKeyShare', {'keyShare': 'a' * 64, 'keyShareName': 'b' * 64})
42
+ assert "'keyShare': '<redacted>'" in caplog.text
43
+ assert 'a' * 50 not in caplog.text
44
+ assert 'b' * 50 + '...' in caplog.text
45
+ assert 'b' * 51 not in caplog.text
sgx_py-0.12.dev0/PKG-INFO DELETED
@@ -1,30 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: sgx.py
3
- Version: 0.12.dev0
4
- Summary: SGX
5
- Home-page: http://github.com/skalenetwork/sgx.py
6
- Author: SKALE Labs
7
- Author-email: support@skalelabs.com
8
- Requires-Python: >=3.11,<4
9
- License-File: LICENSE
10
- Requires-Dist: web3<8.0.0,>=6.20.2
11
- Requires-Dist: pyzmq==27.1.0
12
- Requires-Dist: pem==23.1.0
13
- Requires-Dist: cryptography<47.0.0,>=46.0.1
14
- Provides-Extra: linter
15
- Requires-Dist: ruff==0.13.2; extra == "linter"
16
- Provides-Extra: dev
17
- Requires-Dist: ruff==0.13.2; extra == "dev"
18
- Requires-Dist: coincurve==13.0.0; extra == "dev"
19
- Requires-Dist: python-dotenv==0.13.0; extra == "dev"
20
- Requires-Dist: twine==6.2.0; extra == "dev"
21
- Requires-Dist: pytest==8.4.2; extra == "dev"
22
- Requires-Dist: mock==5.2.0; extra == "dev"
23
- Dynamic: author
24
- Dynamic: author-email
25
- Dynamic: home-page
26
- Dynamic: license-file
27
- Dynamic: provides-extra
28
- Dynamic: requires-dist
29
- Dynamic: requires-python
30
- Dynamic: summary
@@ -1,75 +0,0 @@
1
- # sgx.py
2
- [![Discord](https://img.shields.io/discord/534485763354787851.svg)](https://discord.gg/vvUtWJB)
3
-
4
- ## Requirements
5
-
6
- |**Tool**|**Minimum Supported Version**|
7
- |--------|-----------|
8
- | Python | 3.11 |
9
-
10
- ---
11
-
12
- ## Installation
13
-
14
- ### Install Python 3.11
15
-
16
- ```bash
17
- # confirm prerequisites are isntalled
18
- sudo apt update
19
- sudo apt install software-properties-common
20
- # Add deadsnakes PPA - widely used for Python versions
21
- sudo add-apt-repository ppa:deadsnakes/ppa
22
- # update again & install python 3.11
23
- sudo apt update
24
- sudo apt install python3.11
25
- # Check if it was installed
26
- python3.11 --version
27
-
28
- pip install sgx.py
29
- ```
30
- ---
31
-
32
- ## Developers
33
- ### Install all python libraries
34
- ```bash
35
- sudo apt-get update --fix-missing
36
- sudo apt-get install libudev-dev
37
- sudo apt-get install swig
38
-
39
- # create virtual environment - install dependencies locally
40
- python3.11 -m venv venv
41
- source venv/bin/activate
42
- pip install -e .
43
- pip install -e .[dev]
44
- ```
45
-
46
- ### Running Tests
47
-
48
- Create a `.env` file in source directory, like the following:
49
- ```bash
50
- # SGXWallet listening point (1026 uses https w/ certificates)
51
- SERVER=https://127.0.0.1:1026
52
- # path where you want tests to store the auto-generated certificates
53
- CERT_PATH=.
54
- # private key for the funded test account used by transaction tests
55
- ETH_PRIVATE_KEY=
56
- # Ethereum JSON-RPC endpoint used by transaction tests
57
- GETH=
58
- ```
59
- You may alter the fields as needed.
60
-
61
- Run the tests:
62
- ```bash
63
- # Run all tests
64
- pytest
65
-
66
- # Run specific test file
67
- pytest ./tests/<test-file-name>.py
68
-
69
- ```
70
-
71
- ## License
72
-
73
- [![License](https://img.shields.io/github/license/skalenetwork/sgx.py.svg)](LICENSE)
74
-
75
- Copyright (C) 2019-present SKALE Labs
@@ -1,2 +0,0 @@
1
- # flake8: noqa: F401
2
- from sgx.sgx import SgxClient
@@ -1 +0,0 @@
1
- openssl req -new -sha256 -nodes -out $1 -newkey rsa:2048 -keyout $2 -subj /CN=$3
@@ -1,30 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: sgx.py
3
- Version: 0.12.dev0
4
- Summary: SGX
5
- Home-page: http://github.com/skalenetwork/sgx.py
6
- Author: SKALE Labs
7
- Author-email: support@skalelabs.com
8
- Requires-Python: >=3.11,<4
9
- License-File: LICENSE
10
- Requires-Dist: web3<8.0.0,>=6.20.2
11
- Requires-Dist: pyzmq==27.1.0
12
- Requires-Dist: pem==23.1.0
13
- Requires-Dist: cryptography<47.0.0,>=46.0.1
14
- Provides-Extra: linter
15
- Requires-Dist: ruff==0.13.2; extra == "linter"
16
- Provides-Extra: dev
17
- Requires-Dist: ruff==0.13.2; extra == "dev"
18
- Requires-Dist: coincurve==13.0.0; extra == "dev"
19
- Requires-Dist: python-dotenv==0.13.0; extra == "dev"
20
- Requires-Dist: twine==6.2.0; extra == "dev"
21
- Requires-Dist: pytest==8.4.2; extra == "dev"
22
- Requires-Dist: mock==5.2.0; extra == "dev"
23
- Dynamic: author
24
- Dynamic: author-email
25
- Dynamic: home-page
26
- Dynamic: license-file
27
- Dynamic: provides-extra
28
- Dynamic: requires-dist
29
- Dynamic: requires-python
30
- Dynamic: summary
@@ -1,15 +0,0 @@
1
- web3<8.0.0,>=6.20.2
2
- pyzmq==27.1.0
3
- pem==23.1.0
4
- cryptography<47.0.0,>=46.0.1
5
-
6
- [dev]
7
- ruff==0.13.2
8
- coincurve==13.0.0
9
- python-dotenv==0.13.0
10
- twine==6.2.0
11
- pytest==8.4.2
12
- mock==5.2.0
13
-
14
- [linter]
15
- ruff==0.13.2
File without changes
File without changes