ethernity-cloud-sdk-py 0.2.45__tar.gz → 0.3.0__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.
- {ethernity_cloud_sdk_py-0.2.45/ethernity_cloud_sdk_py.egg-info → ethernity_cloud_sdk_py-0.3.0}/PKG-INFO +1 -1
- ethernity_cloud_sdk_py-0.3.0/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/securelock.py.tmpl +468 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build.py +10 -10
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/ipfs_client.py +2 -2
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/templates/src/ethernity_task.py +1 -1
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0/ethernity_cloud_sdk_py.egg-info}/PKG-INFO +1 -1
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/setup.py +1 -1
- ethernity_cloud_sdk_py-0.2.45/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/securelock.py.tmpl +0 -429
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/LICENSE +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/MANIFEST.in +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/README.md +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/__init__.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/cli.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/__init__.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/__pycache__/__init__.cpython-311.pyc +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/__pycache__/build.cpython-311.pyc +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/__pycache__/config.cpython-311.pyc +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/build.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/config.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/enums.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/init.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/private_key.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/publish.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/__init__.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/Dockerfile.base +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/Dockerfile.base.tpl +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/Dockerfile.tpl +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/scripts/binary-fs-build.sh +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/app/cert1-ca1-clean.crt +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/app/cert1-ca1-clean.key +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/app/enclave_pub_cert.pem +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/app/input.txt +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/app/payload.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/app/public-cert-clean.pem +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/app/result.txt +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/app/transaction.txt +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/etny_crypto.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/etny_exec.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/etny_exec_flask.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/etny_exec_serv.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/image_registry.abi +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/key_generation.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/models.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/pox.abi +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/build/securelock/src/swift_stream_service.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/publish.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/run/__init__.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/run/docker-compose-final.yml.tmpl +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/run/docker-compose-swift-stream.yml.tmpl +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/run/docker-compose.yml.tmpl +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/run/etny-securelock-test.yaml.tpl +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/run/image_registry.abi +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/run/image_registry.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/run/image_registry_runner.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/pynithy/run/public_key_service.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/commands/spinner.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/templates/src/serverless/Dockerfile.serverless +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/templates/src/serverless/__init__.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/templates/src/serverless/backend.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/templates/src/serverless/requirements.txt +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py.egg-info/SOURCES.txt +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py.egg-info/dependency_links.txt +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py.egg-info/entry_points.txt +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py.egg-info/requires.txt +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py.egg-info/top_level.txt +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/setup.cfg +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/tests/__init__.py +0 -0
- {ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/tests/test_example.py +0 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# update 29.12.2022
|
|
3
|
+
import binascii
|
|
4
|
+
import hashlib
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import string
|
|
8
|
+
import io
|
|
9
|
+
import time
|
|
10
|
+
import requests
|
|
11
|
+
from etny_crypto import etny_crypto as crypto
|
|
12
|
+
from web3 import Web3
|
|
13
|
+
from eth_account import Account
|
|
14
|
+
from web3.middleware import geth_poa_middleware
|
|
15
|
+
from key_generation import get_wallet_address
|
|
16
|
+
from etny_exec import execute_task_v3, TaskStatus
|
|
17
|
+
from etny_exec_flask import execute_task_v4, execute_server_v4
|
|
18
|
+
from models import *
|
|
19
|
+
from swift_stream_service import SwiftStreamService
|
|
20
|
+
from eth_account.messages import defunct_hash_message
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
EtnySecureLock: Secure task execution handler for Ethernity Cloud.
|
|
24
|
+
|
|
25
|
+
Ethernity Cloud is a decentralized cloud computing platform that leverages blockchain for secure, private computations.
|
|
26
|
+
This class runs within a trusted enclave (a secure, isolated environment) to handle task execution:
|
|
27
|
+
- Fetches and decrypts user-submitted code (payload) and input data.
|
|
28
|
+
- Validates integrity via checksums and signatures.
|
|
29
|
+
- Executes the task securely.
|
|
30
|
+
- Encrypts and stores results for retrieval.
|
|
31
|
+
It integrates with blockchain smart contracts for metadata and Web3 for interactions, and SwiftStream for encrypted storage.
|
|
32
|
+
Debug mode allows local testing; otherwise, it runs in production enclave setup.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
class EtnySecureLock:
|
|
36
|
+
debug = False
|
|
37
|
+
|
|
38
|
+
def __init__(self, swiftStreamClient):
|
|
39
|
+
"""
|
|
40
|
+
Initialize the EtnySecureLock instance.
|
|
41
|
+
|
|
42
|
+
Sets up file paths, environment variables, blockchain connections, and storage client.
|
|
43
|
+
Uploads the public certificate to storage for verification.
|
|
44
|
+
|
|
45
|
+
:param swiftStreamClient: Client instance for SwiftStream storage service.
|
|
46
|
+
"""
|
|
47
|
+
self.swift_stream_service = swiftStreamClient
|
|
48
|
+
self.version = 'v3.1'
|
|
49
|
+
print(f'ETNY Pynithy [{self.version}]')
|
|
50
|
+
self.__set_initializers()
|
|
51
|
+
self.save_pub_cert()
|
|
52
|
+
self.__load_env()
|
|
53
|
+
self.read_contract_abi()
|
|
54
|
+
self.init_web3()
|
|
55
|
+
self.print_env()
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def read_env_str(env_str):
|
|
59
|
+
"""
|
|
60
|
+
Parse and set environment variables from a string (e.g., content of .env file).
|
|
61
|
+
|
|
62
|
+
Each line is expected in 'KEY=VALUE' format.
|
|
63
|
+
|
|
64
|
+
:param env_str: String containing environment variables.
|
|
65
|
+
"""
|
|
66
|
+
with io.StringIO(env_str) as f:
|
|
67
|
+
for line in f:
|
|
68
|
+
key, value = line.split('=', 1)
|
|
69
|
+
os.environ[key] = re.sub(r'\n', '', value)
|
|
70
|
+
|
|
71
|
+
def __extract_signer(self, checksum, signature):
|
|
72
|
+
"""
|
|
73
|
+
Recover the Ethereum address that signed a given checksum message.
|
|
74
|
+
|
|
75
|
+
Uses Web3's recovery function for signature verification.
|
|
76
|
+
|
|
77
|
+
:param checksum: The message (checksum) that was signed.
|
|
78
|
+
:param signature: The signature to recover from.
|
|
79
|
+
:return: The signer's Ethereum address.
|
|
80
|
+
"""
|
|
81
|
+
signer = self.w3.eth.account.recoverHash(defunct_hash_message(text=checksum), signature=signature)
|
|
82
|
+
return signer
|
|
83
|
+
|
|
84
|
+
def __set_initializers(self):
|
|
85
|
+
"""
|
|
86
|
+
Set initial values for instance variables.
|
|
87
|
+
|
|
88
|
+
Configures file paths, contract addresses, and debug mode overrides.
|
|
89
|
+
In Ethernity Cloud, these paths point to secure enclave-mounted volumes.
|
|
90
|
+
"""
|
|
91
|
+
self.is_valid_client_data = True
|
|
92
|
+
self.key_file = "/private/__SECURELOCK_SESSION__/key.pem"
|
|
93
|
+
self.cert_file = "/app/__SECURELOCK_SESSION__/cert.pem"
|
|
94
|
+
self.pub_cert_file = "/app/__SECURELOCK_SESSION__/enclave_pub_cert.pem"
|
|
95
|
+
self.payload = 'payload.etny.securelock'
|
|
96
|
+
self.input = 'input.txt.securelock'
|
|
97
|
+
self.result_file = '/app/result.txt'
|
|
98
|
+
self.transaction_file = '/app/transaction.txt'
|
|
99
|
+
self.smart_contract_address = '__SMART_CONTRACT_ADDRESS__'
|
|
100
|
+
self.image_registry_address = '__IMAGE_REGISTRY_ADDRESS__'
|
|
101
|
+
self.chain_id = __CHAIN_ID__
|
|
102
|
+
self.web3_provider = '__RPC_URL__'
|
|
103
|
+
self.etny_bucket = "__BUCKET_NAME__"
|
|
104
|
+
self.trusted_zone_public_key = ''
|
|
105
|
+
if EtnySecureLock.debug:
|
|
106
|
+
self.key_file = "./app/cert1-ca1-clean.key"
|
|
107
|
+
self.cert_file = "./app/cert1-ca1-clean.crt"
|
|
108
|
+
self.payload = 'payload.py'
|
|
109
|
+
self.input = 'input.txt'
|
|
110
|
+
self.result_file = './app/result.txt'
|
|
111
|
+
self.transaction_file = './app/transaction.txt'
|
|
112
|
+
self.pub_cert_file = "./app/enclave_pub_cert.pem"
|
|
113
|
+
|
|
114
|
+
def validate_client_payload(self, payload_data, input_data):
|
|
115
|
+
"""
|
|
116
|
+
Validate the integrity of client-submitted payload and input.
|
|
117
|
+
|
|
118
|
+
Fetches task metadata from the blockchain, computes checksums, and verifies signatures.
|
|
119
|
+
Ensures the data matches what was submitted on-chain by the task owner.
|
|
120
|
+
|
|
121
|
+
:param payload_data: Decrypted payload (code to execute).
|
|
122
|
+
:param input_data: Decrypted input data for the task.
|
|
123
|
+
"""
|
|
124
|
+
self.get_do_request_metadata()
|
|
125
|
+
if self._metadata.payload_metadata_obj.checksum is not None:
|
|
126
|
+
payload_checksum = self.compute_sha256_checksum(payload_data)
|
|
127
|
+
if self._metadata.payload_metadata_obj.checksum.startswith('0x'):
|
|
128
|
+
checksum_signer = self.__extract_signer(payload_checksum, self._metadata.payload_metadata_obj.checksum)
|
|
129
|
+
transaction_checksum = payload_checksum
|
|
130
|
+
else:
|
|
131
|
+
checksum_signer = self.order_metadata.do_owner
|
|
132
|
+
transaction_checksum = self._metadata.payload_metadata_obj.checksum
|
|
133
|
+
if checksum_signer.lower() != self.order_metadata.do_owner.lower() and transaction_checksum != payload_checksum:
|
|
134
|
+
self.task_code = TaskStatus.PAYLOAD_CHECKSUM_ERROR
|
|
135
|
+
self.task_result = 'PAYLOAD CHECKSUM DOES NOT MATCH THE EXPECTED VALUE'
|
|
136
|
+
self.is_valid_client_data = False
|
|
137
|
+
return
|
|
138
|
+
if self._metadata.input_metadata_obj.checksum is not None:
|
|
139
|
+
input_checksum = self.compute_sha256_checksum(input_data)
|
|
140
|
+
if self._metadata.input_metadata_obj.checksum.startswith('0x'):
|
|
141
|
+
checksum_signer = self.__extract_signer(input_checksum, self._metadata.input_metadata_obj.checksum)
|
|
142
|
+
transaction_checksum = input_checksum
|
|
143
|
+
else:
|
|
144
|
+
checksum_signer = self.order_metadata.do_owner
|
|
145
|
+
transaction_checksum = self._metadata.input_metadata_obj.checksum
|
|
146
|
+
if checksum_signer.lower() != self.order_metadata.do_owner.lower() and transaction_checksum != input_checksum:
|
|
147
|
+
self.task_code = TaskStatus.INPUT_CHECKSUM_ERROR
|
|
148
|
+
self.task_result = 'INPUT CHECKSUM DOES NOT MATCH THE EXPECTED VALUE'
|
|
149
|
+
self.is_valid_client_data = False
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
def wait_for_payload_and_input(self):
|
|
153
|
+
"""
|
|
154
|
+
Wait for payload and input files to become available in storage.
|
|
155
|
+
|
|
156
|
+
Polls the SwiftStream bucket until both files are present.
|
|
157
|
+
"""
|
|
158
|
+
print('Waiting for payload and input')
|
|
159
|
+
self.wait_for_trustedzone(self.etny_bucket, self.payload)
|
|
160
|
+
self.wait_for_trustedzone(self.etny_bucket, self.input)
|
|
161
|
+
|
|
162
|
+
def wait_for_trustedzone(self, bucket_name, object_name, timeout=3600):
|
|
163
|
+
"""
|
|
164
|
+
Poll for the existence of an object in a SwiftStream bucket.
|
|
165
|
+
|
|
166
|
+
Used to wait for client-uploaded files in the trusted zone.
|
|
167
|
+
|
|
168
|
+
:param bucket_name: Name of the storage bucket.
|
|
169
|
+
:param object_name: Name of the object to wait for.
|
|
170
|
+
:param timeout: Maximum wait time in seconds (default: 1 hour).
|
|
171
|
+
"""
|
|
172
|
+
i = 0
|
|
173
|
+
print(f'Checking if object {object_name} exists in bucket {bucket_name}')
|
|
174
|
+
while True:
|
|
175
|
+
time.sleep(1)
|
|
176
|
+
i = i + 1
|
|
177
|
+
if i > timeout:
|
|
178
|
+
break
|
|
179
|
+
(status, result) = self.__retry_swift_call(self.swift_stream_service.is_object_in_bucket, bucket_name, object_name)
|
|
180
|
+
if status:
|
|
181
|
+
break
|
|
182
|
+
print('secure lock finished the execution')
|
|
183
|
+
|
|
184
|
+
def get_do_request_metadata(self):
|
|
185
|
+
"""
|
|
186
|
+
Fetch metadata for the current task (Distributed Operation Request) from the blockchain.
|
|
187
|
+
|
|
188
|
+
Retrieves order details and metadata using the Ethernity smart contract.
|
|
189
|
+
"""
|
|
190
|
+
order_data = self.__retry_web3_call(self.etny.caller()._getOrder, self.order_id)
|
|
191
|
+
order = Order(order_data, self.order_id)
|
|
192
|
+
metadata_data = self.__retry_web3_call(self.etny.caller()._getDORequestMetadata, order.do_req)
|
|
193
|
+
self._metadata = DOReqMetadata(metadata_data, order.do_req)
|
|
194
|
+
self.order_metadata = order
|
|
195
|
+
|
|
196
|
+
def compute_sha256_checksum(self, file_data):
|
|
197
|
+
"""
|
|
198
|
+
Compute the SHA-256 checksum of given data.
|
|
199
|
+
|
|
200
|
+
Used for integrity validation of payload and input.
|
|
201
|
+
|
|
202
|
+
:param file_data: Data to checksum (string or bytes).
|
|
203
|
+
:return: Hex digest of the checksum.
|
|
204
|
+
"""
|
|
205
|
+
if type(file_data) is str:
|
|
206
|
+
file_data = file_data.encode('utf-8')
|
|
207
|
+
return hashlib.sha256(file_data).hexdigest()
|
|
208
|
+
|
|
209
|
+
def print_env(self):
|
|
210
|
+
"""
|
|
211
|
+
Print key environment and configuration values for logging/debugging.
|
|
212
|
+
"""
|
|
213
|
+
print('ETNY_CHAIN_ID:', self.chain_id)
|
|
214
|
+
print('ETNY_PROTOCOL_CONTRACT_ADDRESS:', self.smart_contract_address)
|
|
215
|
+
print('ETNY_WEB3_PROVIDER:', self.web3_provider)
|
|
216
|
+
print('ETNY_CLIENT_CHALLENGE:', self.client_challenge)
|
|
217
|
+
print('ETNY_ORDER_ID:', self.order_id)
|
|
218
|
+
|
|
219
|
+
def init_web3(self):
|
|
220
|
+
"""
|
|
221
|
+
Initialize Web3 connection to the Ethernity blockchain.
|
|
222
|
+
|
|
223
|
+
Connects to the RPC provider, injects PoA middleware, and sets up contract instances.
|
|
224
|
+
"""
|
|
225
|
+
self.w3 = Web3(Web3.HTTPProvider(self.web3_provider))
|
|
226
|
+
self.w3.middleware_onion.inject(geth_poa_middleware, layer=0)
|
|
227
|
+
self.etny = self.w3.eth.contract(address=self.w3.toChecksumAddress(self.smart_contract_address),
|
|
228
|
+
abi=self.contract_abi)
|
|
229
|
+
self.image_registry = self.w3.eth.contract(address=self.w3.toChecksumAddress(self.image_registry_address),
|
|
230
|
+
abi=self.image_registry_abi)
|
|
231
|
+
|
|
232
|
+
def read_contract_abi(self):
|
|
233
|
+
"""
|
|
234
|
+
Load ABI (Application Binary Interface) for smart contracts.
|
|
235
|
+
|
|
236
|
+
Reads from local files for the main PoX contract and image registry.
|
|
237
|
+
"""
|
|
238
|
+
self.contract_abi = self.__read_contract_abi('pox.abi')
|
|
239
|
+
self.image_registry_abi = self.__read_contract_abi('image_registry.abi')
|
|
240
|
+
|
|
241
|
+
def __read_contract_abi(self, contract_name):
|
|
242
|
+
"""
|
|
243
|
+
Read ABI file content from the script's directory.
|
|
244
|
+
|
|
245
|
+
:param contract_name: Name of the ABI file.
|
|
246
|
+
:return: ABI string.
|
|
247
|
+
"""
|
|
248
|
+
f = open(os.path.dirname(os.path.realpath(__file__)) + f'/{contract_name}')
|
|
249
|
+
contract_abi = f.read()
|
|
250
|
+
f.close()
|
|
251
|
+
return contract_abi
|
|
252
|
+
|
|
253
|
+
def __load_env(self):
|
|
254
|
+
"""
|
|
255
|
+
Load configuration from environment variables.
|
|
256
|
+
|
|
257
|
+
Sets chain ID, contract addresses, RPC URL, client challenge, and order ID.
|
|
258
|
+
These are typically set via the .env file fetched from storage.
|
|
259
|
+
Validates types and sets error states if invalid.
|
|
260
|
+
"""
|
|
261
|
+
try:
|
|
262
|
+
if os.getenv('ETNY_CHAIN_ID') is not None:
|
|
263
|
+
self.chain_id = int(os.getenv('ETNY_CHAIN_ID'))
|
|
264
|
+
if os.getenv('ETNY_SMART_CONTRACT_ADDRESS') is not None:
|
|
265
|
+
self.smart_contract_address = os.getenv('ETNY_SMART_CONTRACT_ADDRESS').rstrip()
|
|
266
|
+
if os.getenv('ETNY_WEB3_PROVIDER') is not None:
|
|
267
|
+
self.web3_provider = os.getenv('ETNY_WEB3_PROVIDER').rstrip()
|
|
268
|
+
self.client_challenge = os.getenv('ETNY_CLIENT_CHALLENGE')
|
|
269
|
+
self.order_id = int(os.getenv('ETNY_ORDER_ID'))
|
|
270
|
+
except ValueError as e:
|
|
271
|
+
print(f'Invalid environment variable type: {e}')
|
|
272
|
+
self.task_code = TaskStatus.SYSTEM_ERROR
|
|
273
|
+
self.task_result = 'INVALID ENVIRONMENT VARIABLES DETECTED DURING LOADING'
|
|
274
|
+
self.is_valid_client_data = False
|
|
275
|
+
|
|
276
|
+
def save_result(self):
|
|
277
|
+
"""
|
|
278
|
+
Save task execution results to storage.
|
|
279
|
+
|
|
280
|
+
Fetches the latest trusted zone public key with retries, encrypts results,
|
|
281
|
+
and uploads them to SwiftStream.
|
|
282
|
+
"""
|
|
283
|
+
self.encrypt_file_and_push_to_swifstream(str(self.task_result), "result.txt")
|
|
284
|
+
self.encrypt_file_and_push_to_swifstream(str(self.task_code), "result_code.txt")
|
|
285
|
+
|
|
286
|
+
def save_pub_cert(self):
|
|
287
|
+
"""
|
|
288
|
+
Upload the enclave's public certificate to storage.
|
|
289
|
+
|
|
290
|
+
Creates the bucket if needed and stores the cert for client verification.
|
|
291
|
+
"""
|
|
292
|
+
self.__ensure_bucket_exists()
|
|
293
|
+
self.__retry_swift_call(self.swift_stream_service.put_file_content, self.etny_bucket,
|
|
294
|
+
"cert.pem",
|
|
295
|
+
self.cert_file)
|
|
296
|
+
|
|
297
|
+
def execute(self):
|
|
298
|
+
"""
|
|
299
|
+
Execute the client-submitted task.
|
|
300
|
+
|
|
301
|
+
Fetches and decrypts payload/input, validates them, and runs the task
|
|
302
|
+
using the appropriate executor based on metadata version.
|
|
303
|
+
Sets task_code and task_result accordingly.
|
|
304
|
+
"""
|
|
305
|
+
payload_data = self.__get_file_content_and_decrypt(self.payload)
|
|
306
|
+
input_data = self.__get_file_content_and_decrypt(self.input)
|
|
307
|
+
print('Validate client payload and input')
|
|
308
|
+
self.validate_client_payload(payload_data, input_data)
|
|
309
|
+
if self.is_valid_client_data:
|
|
310
|
+
print('Client payload and input are valid')
|
|
311
|
+
else:
|
|
312
|
+
print('Client payload and input are NOT valid')
|
|
313
|
+
return
|
|
314
|
+
if self._metadata._payload_metadata_obj._version == 'v3':
|
|
315
|
+
task_result = execute_task_v3(payload_data, input_data)
|
|
316
|
+
else:
|
|
317
|
+
task_result = execute_server_v4(payload_data, input_data)
|
|
318
|
+
self.task_code = str(task_result[0])
|
|
319
|
+
self.task_result = task_result[1]
|
|
320
|
+
|
|
321
|
+
def __get_file_content_and_decrypt(self, object_name):
|
|
322
|
+
"""
|
|
323
|
+
Fetch and decrypt a file from SwiftStream storage.
|
|
324
|
+
|
|
325
|
+
:param object_name: Name of the object to fetch.
|
|
326
|
+
:return: Decrypted content as string.
|
|
327
|
+
"""
|
|
328
|
+
status, encrypted_base64 = self.__retry_swift_call(self.swift_stream_service.get_file_content, self.etny_bucket, object_name)
|
|
329
|
+
if not status:
|
|
330
|
+
print(f'Failed to get {object_name} file')
|
|
331
|
+
raise Exception(f'Failed to get {object_name} file')
|
|
332
|
+
encrypted_tuple = crypto.encrypted_data_from_base64_json(encrypted_base64.encode('utf-8'))
|
|
333
|
+
decrypted_result = crypto.decrypt(self.key_file, encrypted_tuple)
|
|
334
|
+
return decrypted_result.decode('utf-8')
|
|
335
|
+
|
|
336
|
+
def encrypt_file_and_push_to_swifstream(self, file_data, file_name):
|
|
337
|
+
"""
|
|
338
|
+
Encrypt data with trusted zone public key and upload to SwiftStream.
|
|
339
|
+
|
|
340
|
+
Appends '.securelock' to filename for identification.
|
|
341
|
+
|
|
342
|
+
:param file_data: Data to encrypt and upload.
|
|
343
|
+
:param file_name: Base name for the uploaded file.
|
|
344
|
+
"""
|
|
345
|
+
encrypted_input = crypto.encrypt_with_pub_key(self.trusted_zone_public_key, file_data.encode('utf-8'))
|
|
346
|
+
encrypted_input_base64 = crypto.encrypted_data_to_base64_json(encrypted_input)
|
|
347
|
+
file_name = file_name + '.securelock'
|
|
348
|
+
self.__ensure_bucket_exists()
|
|
349
|
+
data = io.BytesIO(encrypted_input_base64)
|
|
350
|
+
status, _ = self.__retry_swift_call(self.swift_stream_service.put_file_content, self.etny_bucket,
|
|
351
|
+
file_name,
|
|
352
|
+
"",
|
|
353
|
+
data)
|
|
354
|
+
if status:
|
|
355
|
+
print(f'File {file_name} encrypted and saved to swift stream successfully')
|
|
356
|
+
|
|
357
|
+
def get_latest_trusted_zone_public_key(self):
|
|
358
|
+
"""
|
|
359
|
+
Fetch the latest public key for the trusted zone from the image registry contract.
|
|
360
|
+
|
|
361
|
+
Uses retry logic with exponential backoff to handle connection issues or timeouts.
|
|
362
|
+
In Ethernity Cloud, the trusted zone refers to secure enclave images registered on-chain.
|
|
363
|
+
Caches the key after successful fetch.
|
|
364
|
+
"""
|
|
365
|
+
if self.trusted_zone_public_key:
|
|
366
|
+
return # Already cached
|
|
367
|
+
print('getting latest public key of the trusted zone enclave')
|
|
368
|
+
try:
|
|
369
|
+
result = self.__retry_web3_call(self.image_registry.caller().getLatestTrustedZoneImageCertPublicKey,
|
|
370
|
+
'__TRUSTED_ZONE_IMAGE__', 'v3')
|
|
371
|
+
self.trusted_zone_public_key = result[1]
|
|
372
|
+
except Exception as e:
|
|
373
|
+
print(f"Failed to get trusted zone public key: {e}")
|
|
374
|
+
self.task_code = TaskStatus.KEY_ERROR
|
|
375
|
+
self.task_result = 'FAILED TO FETCH TRUSTED ZONE PUBLIC KEY FROM IMAGE REGISTRY'
|
|
376
|
+
|
|
377
|
+
def __retry_web3_call(self, fn, *args, **kwargs):
|
|
378
|
+
"""
|
|
379
|
+
Retry wrapper for Web3 calls with exponential backoff.
|
|
380
|
+
|
|
381
|
+
:param fn: The Web3 function to call.
|
|
382
|
+
:param args: Positional arguments for the function.
|
|
383
|
+
:param kwargs: Keyword arguments for the function.
|
|
384
|
+
:return: Result of the function call.
|
|
385
|
+
"""
|
|
386
|
+
retries = 0
|
|
387
|
+
max_retries = 500
|
|
388
|
+
while retries < max_retries:
|
|
389
|
+
try:
|
|
390
|
+
return fn(*args, **kwargs)
|
|
391
|
+
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout, Exception) as e: # Catch broad for resilience
|
|
392
|
+
print(f"Web3 call failed: {e}. Retrying in 1 seconds...")
|
|
393
|
+
time.sleep(1)
|
|
394
|
+
retries += 1
|
|
395
|
+
raise Exception(f"Web3 call failed after {max_retries} retries")
|
|
396
|
+
|
|
397
|
+
def __retry_swift_call(self, fn, *args, **kwargs):
|
|
398
|
+
"""
|
|
399
|
+
Retry wrapper for SwiftStream calls with exponential backoff.
|
|
400
|
+
|
|
401
|
+
:param fn: The SwiftStream function to call.
|
|
402
|
+
:param args: Positional arguments for the function.
|
|
403
|
+
:param kwargs: Keyword arguments for the function.
|
|
404
|
+
:return: Result of the function call.
|
|
405
|
+
"""
|
|
406
|
+
retries = 0
|
|
407
|
+
max_retries = 500
|
|
408
|
+
while retries < max_retries:
|
|
409
|
+
try:
|
|
410
|
+
return fn(*args, **kwargs)
|
|
411
|
+
except Exception as e: # Catch broad for resilience
|
|
412
|
+
print(f"SwiftStream call failed: {e}. Retrying in 1 seconds...")
|
|
413
|
+
time.sleep(1)
|
|
414
|
+
retries += 1
|
|
415
|
+
raise Exception(f"SwiftStream call failed after {max_retries} retries")
|
|
416
|
+
|
|
417
|
+
def __ensure_bucket_exists(self):
|
|
418
|
+
"""
|
|
419
|
+
Ensure the bucket exists in SwiftStream, creating it if necessary.
|
|
420
|
+
|
|
421
|
+
Makes the operation idempotent by checking existence first.
|
|
422
|
+
Assumes swift_stream_service has bucket_exists; if not, wraps create in try-except.
|
|
423
|
+
"""
|
|
424
|
+
try:
|
|
425
|
+
# Assuming SwiftStreamService has bucket_exists method; if not, implement via try-create
|
|
426
|
+
if not self.swift_stream_service.bucket_exists(self.etny_bucket):
|
|
427
|
+
self.__retry_swift_call(self.swift_stream_service.create_bucket, self.etny_bucket)
|
|
428
|
+
except AttributeError:
|
|
429
|
+
# Fallback if bucket_exists not available: try to create and ignore if exists
|
|
430
|
+
try:
|
|
431
|
+
self.__retry_swift_call(self.swift_stream_service.create_bucket, self.etny_bucket)
|
|
432
|
+
except Exception as e:
|
|
433
|
+
if 'already exists' not in str(e).lower(): # Check for existence error
|
|
434
|
+
raise
|
|
435
|
+
|
|
436
|
+
if __name__ == '__main__':
|
|
437
|
+
print('[SecureLock] Loading env variables..')
|
|
438
|
+
try:
|
|
439
|
+
if EtnySecureLock.debug:
|
|
440
|
+
swiftStreamClient = SwiftStreamService("localhost:9000",
|
|
441
|
+
"swiftstreamadmin",
|
|
442
|
+
"swiftstreamadmin")
|
|
443
|
+
else:
|
|
444
|
+
swiftStreamClient = SwiftStreamService("etny-swift-stream:9000",
|
|
445
|
+
"swiftstreamadmin",
|
|
446
|
+
"swiftstreamadmin")
|
|
447
|
+
status, env_content = swiftStreamClient.get_file_content("__BUCKET_NAME__", ".env")
|
|
448
|
+
if not status:
|
|
449
|
+
print("Failed to get .env file")
|
|
450
|
+
raise Exception("Failed to get .env file")
|
|
451
|
+
EtnySecureLock.read_env_str(env_content)
|
|
452
|
+
except:
|
|
453
|
+
cert_file = "/app/__SECURELOCK_SESSION__/cert.pem"
|
|
454
|
+
with open(cert_file, 'r') as f:
|
|
455
|
+
print("PUBLIC_CERT:", f.read())
|
|
456
|
+
exit(1)
|
|
457
|
+
print('Initializing..')
|
|
458
|
+
app = EtnySecureLock(swiftStreamClient)
|
|
459
|
+
print('Validate client payload and input')
|
|
460
|
+
app.get_latest_trusted_zone_public_key()
|
|
461
|
+
app.wait_for_payload_and_input()
|
|
462
|
+
if app.is_valid_client_data:
|
|
463
|
+
print('Executing client code..')
|
|
464
|
+
app.execute()
|
|
465
|
+
else:
|
|
466
|
+
print('Failed client payload and input validation')
|
|
467
|
+
app.save_result()
|
|
468
|
+
print('Finished the execution')
|
|
@@ -111,36 +111,36 @@ def clean_up_registry():
|
|
|
111
111
|
|
|
112
112
|
def copy_backend_to_build_dir(build_dir):
|
|
113
113
|
# Copy serverless source code (including subdirectories) to the build directory
|
|
114
|
-
|
|
114
|
+
|
|
115
115
|
src_dir = Path.cwd() / "src" / "serverless"
|
|
116
116
|
dest_dir = Path(build_dir) / "securelock" / "src" / "serverless"
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
# Remove destination directory if it exists to avoid conflicts
|
|
119
119
|
if dest_dir.exists():
|
|
120
120
|
shutil.rmtree(dest_dir)
|
|
121
121
|
|
|
122
122
|
# Copy entire directory tree
|
|
123
123
|
shutil.copytree(src_dir, dest_dir)
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
return True
|
|
126
126
|
|
|
127
127
|
|
|
128
128
|
def copy_from_module_to_build_dir(build_dir):
|
|
129
129
|
# Copy module files from module dir to build dir
|
|
130
130
|
module_dir = Path(__file__).resolve().parent
|
|
131
|
-
|
|
131
|
+
|
|
132
132
|
build_dir.mkdir(parents=True, exist_ok=True)
|
|
133
133
|
|
|
134
134
|
scripts_dir = build_dir / "securelock" / "scripts"
|
|
135
135
|
scripts_dir.mkdir(parents=True, exist_ok=True)
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
src_file = module_dir / "build" / "securelock" / "Dockerfile.base.tpl"
|
|
138
|
+
src_file = module_dir / "build" / "securelock" / "Dockerfile.base.tpl"
|
|
139
139
|
dest_file = build_dir / "securelock" / "Dockerfile.base.tpl"
|
|
140
140
|
shutil.copy(src_file, dest_file)
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
src_file = module_dir / "build" / "securelock" / "Dockerfile.tpl"
|
|
142
|
+
|
|
143
|
+
src_file = module_dir / "build" / "securelock" / "Dockerfile.tpl"
|
|
144
144
|
dest_file = build_dir / "securelock" / "Dockerfile.tpl"
|
|
145
145
|
shutil.copy(src_file, dest_file)
|
|
146
146
|
|
|
@@ -148,14 +148,14 @@ def copy_from_module_to_build_dir(build_dir):
|
|
|
148
148
|
dest_file = build_dir / "securelock" / "scripts" / "binary-fs-build.sh"
|
|
149
149
|
shutil.copy(src_file, dest_file)
|
|
150
150
|
|
|
151
|
-
src_file = module_dir / "build" / "securelock" / "src"
|
|
151
|
+
src_file = module_dir / "build" / "securelock" / "src"
|
|
152
152
|
dest_file = build_dir / "securelock" / "src"
|
|
153
153
|
# Remove dest if it exists (since copytree fails if dest exists)
|
|
154
154
|
|
|
155
155
|
if dest_file.exists():
|
|
156
156
|
shutil.rmtree(dest_file)
|
|
157
157
|
shutil.copytree(src_file, dest_file)
|
|
158
|
-
|
|
158
|
+
|
|
159
159
|
return True
|
|
160
160
|
|
|
161
161
|
def update_dockerfile():
|
|
@@ -326,7 +326,7 @@ def main():
|
|
|
326
326
|
|
|
327
327
|
# Change directory to the build directory
|
|
328
328
|
os.chdir(build_dir)
|
|
329
|
-
|
|
329
|
+
|
|
330
330
|
spinner.spin_till_done("Update dockerfile ", update_dockerfile)
|
|
331
331
|
|
|
332
332
|
SECURELOCK_SESSION = config.read("SECURELOCK_SESSION")
|
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
import functools
|
|
6
6
|
import random
|
|
7
7
|
from tqdm import tqdm
|
|
8
|
-
from requests.exceptions import RequestException, SSLError
|
|
8
|
+
from requests.exceptions import RequestException, SSLError
|
|
9
9
|
from requests_toolbelt.multipart.encoder import (
|
|
10
10
|
MultipartEncoder,
|
|
11
11
|
MultipartEncoderMonitor,
|
|
@@ -37,7 +37,7 @@ def retry_on_failure(max_attempts=RETRY_COUNT, initial_delay=10, backoff_factor=
|
|
|
37
37
|
return None
|
|
38
38
|
return wrapper
|
|
39
39
|
return decorator_retry
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
|
|
42
42
|
class IPFSClient:
|
|
43
43
|
|
|
@@ -45,7 +45,7 @@ def execute_task(code) -> None:
|
|
|
45
45
|
PASSWORD = getpass.getpass("Enter your private key password:")
|
|
46
46
|
ENC_PRIVATE_KEY = os.getenv("ENC_PRIVATE_KEY")
|
|
47
47
|
pkm = PrivateKeyManager(PASSWORD)
|
|
48
|
-
PRIVATE_KEY = pkm.decrypt_private_key(ENC_PRIVATE_KEY)
|
|
48
|
+
PRIVATE_KEY = '0x' + pkm.decrypt_private_key(ENC_PRIVATE_KEY)
|
|
49
49
|
break
|
|
50
50
|
except Exception as e:
|
|
51
51
|
print("Incorrect password. Please try again.")
|
|
@@ -5,7 +5,7 @@ this_directory = Path(__file__).parent
|
|
|
5
5
|
long_description = (this_directory / "README.md").read_text()
|
|
6
6
|
setup(
|
|
7
7
|
name="ethernity-cloud-sdk-py",
|
|
8
|
-
version="0.
|
|
8
|
+
version="0.3.0",
|
|
9
9
|
url="https://github.com/ethernity-cloud/ethernity-cloud-sdk-py",
|
|
10
10
|
author="Ethernity Cloud Team",
|
|
11
11
|
author_email="contact@ethernity.cloud",
|
|
@@ -1,429 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/python
|
|
2
|
-
|
|
3
|
-
# update 29.12.2022
|
|
4
|
-
import binascii
|
|
5
|
-
import hashlib
|
|
6
|
-
import os
|
|
7
|
-
import re
|
|
8
|
-
import string
|
|
9
|
-
import random
|
|
10
|
-
import io
|
|
11
|
-
import time
|
|
12
|
-
|
|
13
|
-
from etny_crypto import etny_crypto as crypto
|
|
14
|
-
from web3 import Web3
|
|
15
|
-
from eth_account import Account
|
|
16
|
-
from web3.middleware import geth_poa_middleware
|
|
17
|
-
from key_generation import get_wallet_address
|
|
18
|
-
from etny_exec import execute_task_v3, TaskStatus
|
|
19
|
-
from etny_exec_flask import execute_task_v4, execute_server_v4
|
|
20
|
-
from models import *
|
|
21
|
-
from swift_stream_service import SwiftStreamService
|
|
22
|
-
from eth_account.messages import defunct_hash_message
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# todo: remove EtnyResultPoc.debug
|
|
26
|
-
class EtnySecureLock:
|
|
27
|
-
debug = False
|
|
28
|
-
|
|
29
|
-
def __init__(self, swiftStreamClient):
|
|
30
|
-
self.swift_stream_service = swiftStreamClient
|
|
31
|
-
self.version = 'v3'
|
|
32
|
-
print(f'ETNY Pynithy [{self.version}]')
|
|
33
|
-
self.__set_initializers()
|
|
34
|
-
self.save_pub_cert()
|
|
35
|
-
self.__load_env()
|
|
36
|
-
# self.read_client_challenge()
|
|
37
|
-
# self.generate_challenge()
|
|
38
|
-
# self.generate_eth_compatible_wallet()
|
|
39
|
-
# print('wallet = ', self.publickey)
|
|
40
|
-
# print('private = ', self.privatekey)
|
|
41
|
-
self.read_contract_abi()
|
|
42
|
-
self.init_web3()
|
|
43
|
-
self.print_env()
|
|
44
|
-
# if not EtnySecureLock.debug:
|
|
45
|
-
# self.reset_cert_file()
|
|
46
|
-
|
|
47
|
-
@staticmethod
|
|
48
|
-
def read_env(env_file):
|
|
49
|
-
with open(env_file, 'r') as f:
|
|
50
|
-
# Read each line of the file
|
|
51
|
-
for line in f:
|
|
52
|
-
# Split the line at the first '=' character
|
|
53
|
-
key, value = line.split('=', 1)
|
|
54
|
-
# Set the environment variable
|
|
55
|
-
os.environ[key] = value
|
|
56
|
-
|
|
57
|
-
@staticmethod
|
|
58
|
-
def read_env_str(env_str):
|
|
59
|
-
with io.StringIO(env_str) as f:
|
|
60
|
-
# Read each line of the string
|
|
61
|
-
for line in f:
|
|
62
|
-
# Split the line at the first '=' character
|
|
63
|
-
key, value = line.split('=', 1)
|
|
64
|
-
# Set the environment variable and remove new line
|
|
65
|
-
os.environ[key] = re.sub(r'\n', '', value)
|
|
66
|
-
|
|
67
|
-
def __extract_signer(self, checksum, signature):
|
|
68
|
-
signer = self.w3.eth.account.recoverHash(defunct_hash_message(text=checksum), signature=signature)
|
|
69
|
-
|
|
70
|
-
return signer
|
|
71
|
-
|
|
72
|
-
def __set_initializers(self):
|
|
73
|
-
self.is_valid_client_data = True
|
|
74
|
-
self.key_file = "/private/__SECURELOCK_SESSION__/key.pem"
|
|
75
|
-
self.cert_file = "/app/__SECURELOCK_SESSION__/cert.pem"
|
|
76
|
-
self.pub_cert_file = "/app/__SECURELOCK_SESSION__/enclave_pub_cert.pem"
|
|
77
|
-
self.payload = 'payload.etny.securelock'
|
|
78
|
-
self.input = 'input.txt.securelock'
|
|
79
|
-
self.result_file = '/app/result.txt'
|
|
80
|
-
self.transaction_file = '/app/transaction.txt'
|
|
81
|
-
self.smart_contract_address = '__SMART_CONTRACT_ADDRESS__'
|
|
82
|
-
self.image_registry_address = '__IMAGE_REGISTRY_ADDRESS__'
|
|
83
|
-
self.chain_id = __CHAIN_ID__
|
|
84
|
-
self.web3_provider = '__RPC_URL__'
|
|
85
|
-
self.etny_bucket = "__BUCKET_NAME__"
|
|
86
|
-
self.trusted_zone_public_key = ''
|
|
87
|
-
if EtnySecureLock.debug:
|
|
88
|
-
self.key_file = "./app/cert1-ca1-clean.key"
|
|
89
|
-
self.cert_file = "./app/cert1-ca1-clean.crt"
|
|
90
|
-
self.payload = 'payload.py'
|
|
91
|
-
self.input = 'input.txt'
|
|
92
|
-
self.result_file = './app/result.txt'
|
|
93
|
-
self.transaction_file = './app/transaction.txt'
|
|
94
|
-
self.pub_cert_file = "./app/enclave_pub_cert.pem"
|
|
95
|
-
# self.trustedzone_public_key = "./app/enclave_pub_cert.pem"
|
|
96
|
-
|
|
97
|
-
def validate_client_payload(self, payload_data, input_data):
|
|
98
|
-
self.get_do_request_metadata()
|
|
99
|
-
if self._metadata.payload_metadata_obj.checksum is not None:
|
|
100
|
-
#print('Computing payload checksum: ')
|
|
101
|
-
payload_checksum = self.compute_sha256_checksum(payload_data)
|
|
102
|
-
#print('payload checksum: ', payload_checksum)
|
|
103
|
-
# check the wallet address that signed payload checksum is the one from order metadata
|
|
104
|
-
if self._metadata.payload_metadata_obj.checksum.startswith('0x'):
|
|
105
|
-
checksum_signer = self.__extract_signer(payload_checksum, self._metadata.payload_metadata_obj.checksum)
|
|
106
|
-
transaction_checksum = payload_checksum
|
|
107
|
-
else:
|
|
108
|
-
checksum_signer = self.order_metadata.do_owner
|
|
109
|
-
transaction_checksum = self._metadata.payload_metadata_obj.checksum
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if checksum_signer.lower() != self.order_metadata.do_owner.lower() and transaction_checksum != payload_checksum:
|
|
113
|
-
self.task_code = TaskStatus.PAYLOAD_CHECKSUM_ERROR
|
|
114
|
-
self.task_result = 'PAYLOAD CHECKSUM DOESN\'T MATCH'
|
|
115
|
-
self.is_valid_client_data = False
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
if self._metadata.input_metadata_obj.checksum is not None:
|
|
119
|
-
input_checksum = self.compute_sha256_checksum(input_data)
|
|
120
|
-
#print('input checksum: ', input_checksum)
|
|
121
|
-
# check the wallet address that signed input checksum is the one from order metadata
|
|
122
|
-
if self._metadata.input_metadata_obj.checksum.startswith('0x'):
|
|
123
|
-
checksum_signer = self.__extract_signer(input_checksum, self._metadata.input_metadata_obj.checksum)
|
|
124
|
-
transaction_checksum = input_checksum
|
|
125
|
-
else:
|
|
126
|
-
checksum_signer = self.order_metadata.do_owner
|
|
127
|
-
transaction_checksum = self._metadata.input_metadata_obj.checksum
|
|
128
|
-
|
|
129
|
-
if checksum_signer.lower() != self.order_metadata.do_owner.lower() and transaction_checksum != input_checksum:
|
|
130
|
-
self.task_code = TaskStatus.INPUT_CHECKSUM_ERROR
|
|
131
|
-
self.task_result = 'INPUT CHECKSUM DOESN\'T MATCH'
|
|
132
|
-
self.is_valid_client_data = False
|
|
133
|
-
return
|
|
134
|
-
|
|
135
|
-
def wait_for_payload_and_input(self):
|
|
136
|
-
print('Waiting for payload and input')
|
|
137
|
-
self.wait_for_trustedzone(self.etny_bucket, self.payload)
|
|
138
|
-
self.wait_for_trustedzone(self.etny_bucket, self.input)
|
|
139
|
-
|
|
140
|
-
def wait_for_trustedzone(self, bucket_name, object_name, timeout=3600):
|
|
141
|
-
i = 0
|
|
142
|
-
print(f'Checking if object {object_name} exists in bucket {bucket_name}')
|
|
143
|
-
while True:
|
|
144
|
-
time.sleep(1)
|
|
145
|
-
i = i + 1
|
|
146
|
-
if i > timeout:
|
|
147
|
-
break
|
|
148
|
-
(status, result) = self.swift_stream_service.is_object_in_bucket(bucket_name, object_name)
|
|
149
|
-
if status:
|
|
150
|
-
break
|
|
151
|
-
|
|
152
|
-
print('secure lock finished the execution')
|
|
153
|
-
|
|
154
|
-
def get_do_request_metadata(self):
|
|
155
|
-
order = Order(self.etny.caller()._getOrder(self.order_id), self.order_id)
|
|
156
|
-
#print('order', order)
|
|
157
|
-
self._metadata = DOReqMetadata(self.etny.caller()._getDORequestMetadata(order.do_req), order.do_req)
|
|
158
|
-
self.order_metadata = order
|
|
159
|
-
#print('metadata', self._metadata)
|
|
160
|
-
|
|
161
|
-
def compute_sha256_checksum(self, file_data):
|
|
162
|
-
if type(file_data) is str:
|
|
163
|
-
file_data = file_data.encode('utf-8')
|
|
164
|
-
|
|
165
|
-
return hashlib.sha256(file_data).hexdigest()
|
|
166
|
-
|
|
167
|
-
# h = hashlib.sha256()
|
|
168
|
-
# file = io.BytesIO(file_data)
|
|
169
|
-
# while True:
|
|
170
|
-
# Reading is buffered, so we can read smaller chunks.
|
|
171
|
-
# chunk = file.read(h.block_size)
|
|
172
|
-
# if not chunk:
|
|
173
|
-
# break
|
|
174
|
-
# h.update(chunk)
|
|
175
|
-
# return h.hexdigest()
|
|
176
|
-
|
|
177
|
-
def reset_cert_file(self):
|
|
178
|
-
#print('Resetting the cert file')
|
|
179
|
-
try:
|
|
180
|
-
with open(self.key_file, 'r+') as f:
|
|
181
|
-
f.truncate(0)
|
|
182
|
-
except Exception as e:
|
|
183
|
-
print('Error while removing the private key file content', e)
|
|
184
|
-
|
|
185
|
-
#with open(self.key_file, 'r') as f:
|
|
186
|
-
# print('cert file content', f.read(), '##################')
|
|
187
|
-
|
|
188
|
-
def generate_eth_compatible_wallet(self):
|
|
189
|
-
decrypted_string = crypto.decrypt(
|
|
190
|
-
private_key_file=self.key_file,
|
|
191
|
-
encrypted_msg=self.encrypted_tuple
|
|
192
|
-
)
|
|
193
|
-
#print('decrypted_string = ', decrypted_string)
|
|
194
|
-
(public, private) = get_wallet_address(decrypted_string.decode(), self.enclave_challenge)
|
|
195
|
-
self.publickey = public
|
|
196
|
-
self.privatekey = private
|
|
197
|
-
|
|
198
|
-
def print_env(self):
|
|
199
|
-
print('chain id:', self.chain_id)
|
|
200
|
-
print('smart contract address:', self.smart_contract_address)
|
|
201
|
-
print('web3 provider:', self.web3_provider)
|
|
202
|
-
print('encrypted challenge:', self.client_challenge)
|
|
203
|
-
print('order id:', self.order_id)
|
|
204
|
-
|
|
205
|
-
def init_web3(self):
|
|
206
|
-
self.w3 = Web3(Web3.HTTPProvider(self.web3_provider))
|
|
207
|
-
self.w3.middleware_onion.inject(geth_poa_middleware, layer=0)
|
|
208
|
-
|
|
209
|
-
# self.acct = Account.privateKeyToAccount(self.privatekey)
|
|
210
|
-
self.etny = self.w3.eth.contract(address=self.w3.toChecksumAddress(self.smart_contract_address),
|
|
211
|
-
abi=self.contract_abi)
|
|
212
|
-
self.image_registry = self.w3.eth.contract(address=self.w3.toChecksumAddress(self.image_registry_address),
|
|
213
|
-
abi=self.image_registry_abi)
|
|
214
|
-
|
|
215
|
-
def read_contract_abi(self):
|
|
216
|
-
self.contract_abi = self.__read_contract_abi('pox.abi')
|
|
217
|
-
self.image_registry_abi = self.__read_contract_abi('image_registry.abi')
|
|
218
|
-
|
|
219
|
-
def __read_contract_abi(self, contract_name):
|
|
220
|
-
f = open(os.path.dirname(os.path.realpath(__file__)) + f'/{contract_name}')
|
|
221
|
-
contract_abi = f.read()
|
|
222
|
-
f.close()
|
|
223
|
-
return contract_abi
|
|
224
|
-
|
|
225
|
-
def __load_env(self):
|
|
226
|
-
if os.getenv('ETNY_CHAIN_ID') is not None:
|
|
227
|
-
self.chain_id = int(os.getenv('ETNY_CHAIN_ID'))
|
|
228
|
-
|
|
229
|
-
if os.getenv('ETNY_SMART_CONTRACT_ADDRESS') is not None:
|
|
230
|
-
self.smart_contract_address = os.getenv('ETNY_SMART_CONTRACT_ADDRESS').rstrip()
|
|
231
|
-
|
|
232
|
-
if os.getenv('ETNY_WEB3_PROVIDER') is not None:
|
|
233
|
-
self.web3_provider = os.getenv('ETNY_WEB3_PROVIDER').rstrip()
|
|
234
|
-
|
|
235
|
-
self.client_challenge = os.getenv('ETNY_CLIENT_CHALLENGE')
|
|
236
|
-
self.order_id = int(os.getenv('ETNY_ORDER_ID'))
|
|
237
|
-
|
|
238
|
-
def read_client_challenge(self):
|
|
239
|
-
self.encrypted_tuple = crypto.encrypted_data_from_base64_json(self.client_challenge)
|
|
240
|
-
|
|
241
|
-
def generate_challenge(self, length=20):
|
|
242
|
-
lowercase_only = ''.join(
|
|
243
|
-
random.choice(string.ascii_lowercase) for _ in range(length)
|
|
244
|
-
)
|
|
245
|
-
#print('random challenge:', lowercase_only)
|
|
246
|
-
self.enclave_challenge = lowercase_only
|
|
247
|
-
return self.enclave_challenge
|
|
248
|
-
|
|
249
|
-
def build_result(self):
|
|
250
|
-
self.result = (f'{self.version}:{self.task_code}:{self.task_result_checksum}:{self.enclave_challenge}:')
|
|
251
|
-
|
|
252
|
-
def addResult(self):
|
|
253
|
-
self.build_result()
|
|
254
|
-
print(f'adding result to order {self.order_id}')
|
|
255
|
-
nonce = self.w3.eth.getTransactionCount(self.publickey)
|
|
256
|
-
unicorn_txn = self.etny.functions._addResultToOrder(
|
|
257
|
-
self.order_id, self.result
|
|
258
|
-
).buildTransaction({
|
|
259
|
-
'gas': 1000000,
|
|
260
|
-
'chainId': self.chain_id,
|
|
261
|
-
'nonce': nonce,
|
|
262
|
-
'gasPrice': self.w3.toWei("1", "mwei"),
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
signed_txn = self.w3.eth.account.sign_transaction(unicorn_txn, private_key=self.acct.key)
|
|
266
|
-
self.w3.eth.sendRawTransaction(signed_txn.rawTransaction)
|
|
267
|
-
# self.transaction = unicorn_tx signed
|
|
268
|
-
hash = self.w3.toHex(self.w3.sha3(signed_txn.rawTransaction))
|
|
269
|
-
|
|
270
|
-
try:
|
|
271
|
-
receipt = self.w3.eth.waitForTransactionReceipt(hash)
|
|
272
|
-
except Exception as e:
|
|
273
|
-
print(f'An error occurred while sending transaction result {self.order_id}: ', e)
|
|
274
|
-
raise
|
|
275
|
-
|
|
276
|
-
#print('transaction status: ', receipt.status)
|
|
277
|
-
#print('transaction receipt: ', receipt)
|
|
278
|
-
if receipt.status == 1:
|
|
279
|
-
print("Result transaction was successful!")
|
|
280
|
-
else:
|
|
281
|
-
print("Result transaction was UNSUCCESSFUL!")
|
|
282
|
-
|
|
283
|
-
#print("Result payload: %s" % self.result)
|
|
284
|
-
#print("TX Raw: %s" % signed_txn.rawTransaction)
|
|
285
|
-
#print("TX Hash: %s" % hash)
|
|
286
|
-
|
|
287
|
-
def build_transaction(self):
|
|
288
|
-
self.build_result()
|
|
289
|
-
print(f'adding result to order {self.order_id}')
|
|
290
|
-
nonce = self.w3.eth.getTransactionCount(self.publickey)
|
|
291
|
-
unicorn_txn = self.etny.functions._addResultToOrder(
|
|
292
|
-
self.order_id, self.result
|
|
293
|
-
).buildTransaction({
|
|
294
|
-
'gas': 1000000,
|
|
295
|
-
'chainId': self.chain_id,
|
|
296
|
-
'nonce': nonce,
|
|
297
|
-
'gasPrice': self.w3.toWei("1", "mwei"),
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
signed_txn = self.w3.eth.account.sign_transaction(unicorn_txn, private_key=self.acct.key)
|
|
301
|
-
signed_tx_as_bytes = binascii.hexlify(signed_txn.rawTransaction)
|
|
302
|
-
self.signed_tx_as_bytes = signed_tx_as_bytes
|
|
303
|
-
|
|
304
|
-
def get_result_checksum(self):
|
|
305
|
-
self.task_result_checksum = hashlib.sha256(self.task_result.encode("utf-8")).hexdigest()
|
|
306
|
-
|
|
307
|
-
def save_result(self):
|
|
308
|
-
self.__get_latest_trusted_zone_public_key()
|
|
309
|
-
self.encrypt_file_and_push_to_swifstream(str(self.task_result), "result.txt")
|
|
310
|
-
self.encrypt_file_and_push_to_swifstream(str(self.task_code), "result_code.txt")
|
|
311
|
-
|
|
312
|
-
def save_pub_cert(self):
|
|
313
|
-
self.swift_stream_service.create_bucket(self.etny_bucket)
|
|
314
|
-
self.swift_stream_service.put_file_content(self.etny_bucket,
|
|
315
|
-
"cert.pem",
|
|
316
|
-
self.cert_file)
|
|
317
|
-
|
|
318
|
-
def save_transaction(self):
|
|
319
|
-
self.swift_stream_service.create_bucket(self.etny_bucket)
|
|
320
|
-
transaction = io.BytesIO(self.signed_tx_as_bytes)
|
|
321
|
-
self.swift_stream_service.put_file_content(self.etny_bucket,
|
|
322
|
-
"transaction.txt",
|
|
323
|
-
self.transaction_file,
|
|
324
|
-
transaction)
|
|
325
|
-
|
|
326
|
-
def execute(self):
|
|
327
|
-
payload_data = self.__get_file_content_and_decrypt(self.payload)
|
|
328
|
-
input_data = self.__get_file_content_and_decrypt(self.input)
|
|
329
|
-
print('Validate client payload and input')
|
|
330
|
-
self.validate_client_payload(payload_data, input_data)
|
|
331
|
-
if app.is_valid_client_data:
|
|
332
|
-
print('Client payload and input are valid')
|
|
333
|
-
else:
|
|
334
|
-
print('Client payload and input are NOT valid')
|
|
335
|
-
return
|
|
336
|
-
|
|
337
|
-
if self._metadata._payload_metadata_obj._version == 'v3':
|
|
338
|
-
task_result = execute_task_v3(payload_data, input_data)
|
|
339
|
-
else:
|
|
340
|
-
task_result = execute_server_v4(payload_data, input_data)
|
|
341
|
-
|
|
342
|
-
self.task_code = str(task_result[0])
|
|
343
|
-
self.task_result = task_result[1]
|
|
344
|
-
|
|
345
|
-
def __get_file_content_and_decrypt(self, object_name):
|
|
346
|
-
status, encrypted_base64 = self.swift_stream_service.get_file_content(self.etny_bucket, object_name)
|
|
347
|
-
if not status:
|
|
348
|
-
print(f'Failed to get {object_name} file')
|
|
349
|
-
raise Exception(f'Failed to get {object_name} file')
|
|
350
|
-
|
|
351
|
-
encrypted_tuple = crypto.encrypted_data_from_base64_json(encrypted_base64.encode('utf-8'))
|
|
352
|
-
decrypted_result = crypto.decrypt(self.key_file, encrypted_tuple)
|
|
353
|
-
return decrypted_result.decode('utf-8')
|
|
354
|
-
|
|
355
|
-
def encrypt_file_and_push_to_swifstream(self, file_data, file_name):
|
|
356
|
-
encrypted_input = crypto.encrypt_with_pub_key(self.trusted_zone_public_key, file_data.encode('utf-8'))
|
|
357
|
-
encrypted_input_base64 = crypto.encrypted_data_to_base64_json(encrypted_input)
|
|
358
|
-
|
|
359
|
-
# file name should end with secure lock '.securelock' extension
|
|
360
|
-
file_name = file_name + '.securelock'
|
|
361
|
-
self.swift_stream_service.create_bucket(self.etny_bucket)
|
|
362
|
-
data = io.BytesIO(encrypted_input_base64)
|
|
363
|
-
status, _ = self.swift_stream_service.put_file_content(self.etny_bucket,
|
|
364
|
-
file_name,
|
|
365
|
-
"",
|
|
366
|
-
data)
|
|
367
|
-
if status:
|
|
368
|
-
print(f'File {file_name} encrypted and saved to swift stream successfully')
|
|
369
|
-
|
|
370
|
-
def __get_trusted_zone_public_key(self):
|
|
371
|
-
print('getting the public key of the trusted zone enclave')
|
|
372
|
-
image_hash = self._metadata.image_hash
|
|
373
|
-
if EtnySecureLock.debug:
|
|
374
|
-
image_hash = 'v3-hash-1'
|
|
375
|
-
self.trusted_zone_public_key = self.image_registry.caller().getTrustedZoneImageCertPublicKey(
|
|
376
|
-
image_hash)
|
|
377
|
-
|
|
378
|
-
def __get_latest_trusted_zone_public_key(self):
|
|
379
|
-
print('getting latest public key of the trusted zone enclave')
|
|
380
|
-
self.trusted_zone_public_key = self.image_registry.caller().getLatestTrustedZoneImageCertPublicKey(
|
|
381
|
-
'__TRUSTED_ZONE_IMAGE__', 'v3')[1]
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
if __name__ == '__main__':
|
|
385
|
-
print('[SecureLock] Loading env variables..')
|
|
386
|
-
try:
|
|
387
|
-
if EtnySecureLock.debug:
|
|
388
|
-
swiftStreamClient = SwiftStreamService("localhost:9000",
|
|
389
|
-
"swiftstreamadmin",
|
|
390
|
-
"swiftstreamadmin")
|
|
391
|
-
else:
|
|
392
|
-
swiftStreamClient = SwiftStreamService("etny-swift-stream:9000",
|
|
393
|
-
"swiftstreamadmin",
|
|
394
|
-
"swiftstreamadmin")
|
|
395
|
-
|
|
396
|
-
status, env_content = swiftStreamClient.get_file_content("__BUCKET_NAME__", ".env")
|
|
397
|
-
if not status:
|
|
398
|
-
print("Failed to get .env file")
|
|
399
|
-
raise Exception("Failed to get .env file")
|
|
400
|
-
EtnySecureLock.read_env_str(env_content)
|
|
401
|
-
except:
|
|
402
|
-
cert_file = "/app/__SECURELOCK_SESSION__/cert.pem"
|
|
403
|
-
with open(cert_file, 'r') as f:
|
|
404
|
-
print("PUBLIC_CERT:", f.read())
|
|
405
|
-
exit(1)
|
|
406
|
-
|
|
407
|
-
#if not EtnySecureLock.debug:
|
|
408
|
-
# with open("/app/__SECURELOCK_SESSION__/cert.pem", 'r') as f:
|
|
409
|
-
# print("PUBLIC_CERT:", f.read())
|
|
410
|
-
|
|
411
|
-
print('Initializing..')
|
|
412
|
-
app = EtnySecureLock(swiftStreamClient)
|
|
413
|
-
print('Validate client payload and input')
|
|
414
|
-
app.wait_for_payload_and_input()
|
|
415
|
-
|
|
416
|
-
if app.is_valid_client_data:
|
|
417
|
-
print('Executing client code..')
|
|
418
|
-
app.execute()
|
|
419
|
-
else:
|
|
420
|
-
print('Failed client payload and input validation')
|
|
421
|
-
# print('Generating task result checksum..')
|
|
422
|
-
# app.get_result_checksum()
|
|
423
|
-
# print('Building transaction..')
|
|
424
|
-
# app.build_transaction()
|
|
425
|
-
# print('Saving transaction...')
|
|
426
|
-
# app.save_transaction()
|
|
427
|
-
# print('Saving the result..')
|
|
428
|
-
app.save_result()
|
|
429
|
-
print('Finished the execution')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/__init__.py
RENAMED
|
File without changes
|
{ethernity_cloud_sdk_py-0.2.45 → ethernity_cloud_sdk_py-0.3.0}/ethernity_cloud_sdk_py/cli.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|