Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Python bytecode files
__pycache__/
*.py[cod]
*$py.class

# Virtual environment directories
venv/
env/
.venv/
ENV/
env.bak/
venv.bak/

# Distribution / packaging
.Python
build/
dist/
*.egg-info/
*.eggs/
.eggs/

# IDEs and text editors
.idea/
.vscode/
*.swp
*.swo
*.bak
*.tmp

# macOS files
.DS_Store

# Windows files
Thumbs.db
ehthumbs.db

# Log files
*.log

# Jupyter Notebook checkpoints
.ipynb_checkpoints/

# Test and coverage reports
.coverage
.coverage.*
nosetests.xml
test_*.xml
pytest*
*.tox/
*.nox/
*.coverage
*.hypothesis/

# Django settings
*.log
*.pot
*.pyc
*.pyo
*.db
*.sqlite3
Empty file added __init__.py
Empty file.
Empty file added private/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions private/encryption/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .encryption import encrypt, decrypt, derive_key, make_gcm_cipher

__all__ = ["encrypt", "decrypt", "derive_key", "make_gcm_cipher"]
49 changes: 49 additions & 0 deletions private/encryption/encryption.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import os
import hashlib
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend

KEY_LENGTH = 32


def derive_key(key: bytes, info: bytes) -> bytes:
hkdf = HKDF(
algorithm=hashes.SHA256(),
length=KEY_LENGTH,
salt=None,
info=info,
backend=default_backend(),
)
return hkdf.derive(key)


def make_gcm_cipher(origin_key: bytes, info: bytes):
key = derive_key(origin_key, info)
cipher = Cipher(algorithms.AES(key), modes.GCM(os.urandom(12)), backend=default_backend())
return cipher


def encrypt(key: bytes, data: bytes, info: bytes) -> bytes:
cipher = make_gcm_cipher(key, info)
encryptor = cipher.encryptor()
nonce = encryptor._ctx._nonce # Get the automatically generated nonce
ciphertext = encryptor.update(data) + encryptor.finalize()
return nonce + ciphertext + encryptor.tag


def decrypt(key: bytes, encrypted_data: bytes, info: bytes) -> bytes:
nonce_size = 12 # AES-GCM standard nonce size
tag_size = 16 # AES-GCM standard tag size
if len(encrypted_data) < nonce_size + tag_size:
raise ValueError("Invalid encrypted data")

nonce = encrypted_data[:nonce_size]
ciphertext = encrypted_data[nonce_size:-tag_size]
tag = encrypted_data[-tag_size:]

key = derive_key(key, info)
cipher = Cipher(algorithms.AES(key), modes.GCM(nonce, tag), backend=default_backend())
decryptor = cipher.decryptor()
return decryptor.update(ciphertext) + decryptor.finalize()
Empty file added private/ipc/__init__.py
Empty file.
110 changes: 110 additions & 0 deletions private/ipc/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import time
from web3 import Web3
from web3.middleware import geth_poa_middleware
from eth_account import Account
from private.ipc import contracts

class Config:
"""Configuration for the Ethereum storage contract client."""
def __init__(self, dial_uri, private_key, storage_contract_address="", access_contract_address=""):
self.dial_uri = dial_uri
self.private_key = private_key
self.storage_contract_address = storage_contract_address
self.access_contract_address = access_contract_address

@staticmethod
def default():
return Config(dial_uri="", private_key="", storage_contract_address="", access_contract_address="")

class Client:
"""Represents the Ethereum storage client."""
def __init__(self, web3, storage_contract, access_manager, account):
self.web3 = web3
self.storage = storage_contract
self.access_manager = access_manager
self.account = account
self.ticker = 0.2 # 200ms polling interval

@classmethod
def dial(cls, config):
"""Creates an Ethereum client and initializes smart contract instances."""
web3 = Web3(Web3.HTTPProvider(config.dial_uri))

if not web3.is_connected():
raise ConnectionError("Failed to connect to Ethereum node")

web3.middleware_onion.inject(geth_poa_middleware, layer=0) # Inject middleware for POA chains if needed

account = Account.from_key(config.private_key)

storage_contract = web3.eth.contract(
address=Web3.to_checksum_address(config.storage_contract_address),
abi=contracts.Storage.abi
)

access_manager = web3.eth.contract(
address=Web3.to_checksum_address(config.access_contract_address),
abi=contracts.AccessManager.abi
)

return cls(web3, storage_contract, access_manager, account)

@classmethod
def deploy_storage(cls, config: Config):
"""Deploys a storage smart contract and returns its client and address."""

web3 = Web3(Web3.HTTPProvider(config.dial_uri))

if not web3.is_connected():
raise ConnectionError("Failed to connect to Ethereum node")

web3.middleware_onion.inject(geth_poa_middleware, layer=0) # Inject middleware for POA chains if needed

account = Account.from_key(config.private_key)

# Get current gas price
gas_price = web3.eth.gas_price

# Deploy Storage Contract
storage_contract = web3.eth.contract(abi=contracts.Storage.abi, bytecode=contracts.Storage.bytecode)
tx = storage_contract.constructor().build_transaction({
'from': account.address,
'gas': 5000000,
'gasPrice': gas_price,
'nonce': web3.eth.get_transaction_count(account.address)
})
signed_tx = account.sign_transaction(tx)
tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
storage_receipt = cls.wait_for_tx(web3, tx_hash)
storage_address = storage_receipt.contractAddress

# Deploy Access Manager Contract
access_manager_contract = web3.eth.contract(abi=contracts.AccessManager.abi, bytecode=contracts.AccessManager.bytecode)
tx = access_manager_contract.constructor(storage_address).build_transaction({
'from': account.address,
'gas': 5000000,
'gasPrice': gas_price,
'nonce': web3.eth.get_transaction_count(account.address)
})
signed_tx = account.sign_transaction(tx)
tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
access_manager_receipt = cls.wait_for_tx(web3, tx_hash)
access_manager_address = access_manager_receipt.contractAddress

storage_instance = web3.eth.contract(address=storage_address, abi=contracts.Storage.abi)
access_manager_instance = web3.eth.contract(address=access_manager_address, abi=contracts.AccessManager.abi)

return cls(web3, storage_instance, access_manager_instance, account), storage_address

def wait_for_tx(self, tx_hash, timeout=60):
"""Waits for a transaction to be confirmed."""
start_time = time.time()
while time.time() - start_time < timeout:
receipt = self.web3.eth.get_transaction_receipt(tx_hash)
if receipt:
if receipt.status == 1:
return receipt
else:
raise RuntimeError("Transaction failed")
time.sleep(0.2)
raise TimeoutError("Transaction timeout")
Empty file.
Empty file.
Empty file added private/ipc/errors.py
Empty file.
Empty file added private/memory/__init__.py
Empty file.
57 changes: 57 additions & 0 deletions private/memory/memory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
class Size:
B = 1
KiB = B << 10
MiB = KiB << 10
GiB = MiB << 10
TiB = GiB << 10
PiB = TiB << 10
EiB = PiB << 10

KB = int(1e3)
MB = int(1e6)
GB = int(1e9)
TB = int(1e12)
PB = int(1e15)
EB = int(1e18)

def __init__(self, size: int):
self.size = size

def __str__(self) -> str:
return self.format_size()

def to_int(self) -> int:
return self.size

def mul_int(self, n: int) -> 'Size':
return Size(self.size * n)

def div_int(self, n: int) -> 'Size':
return Size(self.size // n)

def format_size(self) -> str:
if self.size >= self.EB:
return f"{self.size / self.EB:.2f}EB"
elif self.size >= self.PB:
return f"{self.size / self.PB:.2f}PB"
elif self.size >= self.TB:
return f"{self.size / self.TB:.2f}TB"
elif self.size >= self.GB:
return f"{self.size / self.GB:.2f}GB"
elif self.size >= self.MB:
return f"{self.size / self.MB:.2f}MB"
elif self.size >= self.KB:
return f"{self.size / self.KB:.2f}KB"
else:
return f"{self.size}B"

@staticmethod
def format_bytes(bytes_size: int) -> str:
if bytes_size >= Size.GB:
return f"{bytes_size // Size.GB} GB"
elif bytes_size >= Size.MB:
return f"{bytes_size // Size.MB} MB"
elif bytes_size >= Size.KB:
return f"{bytes_size // Size.KB} KB"
else:
return f"{bytes_size} Bytes"
Empty file added private/pb/__init__.py
Empty file.
Loading