indro 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- indro/__init__.py +11 -0
- indro/client.py +267 -0
- indro-0.0.1.dist-info/METADATA +61 -0
- indro-0.0.1.dist-info/RECORD +6 -0
- indro-0.0.1.dist-info/WHEEL +5 -0
- indro-0.0.1.dist-info/top_level.txt +1 -0
indro/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# ==========================================
|
|
2
|
+
# VANGUARD TITAN SDK - INITIALIZATION
|
|
3
|
+
# ==========================================
|
|
4
|
+
|
|
5
|
+
from .client import IndroVault
|
|
6
|
+
|
|
7
|
+
__version__ = "16.0.0"
|
|
8
|
+
|
|
9
|
+
# This exposes IndroVault so developers can just do:
|
|
10
|
+
# from indro_vanguard import IndroVault
|
|
11
|
+
__all__ = ["IndroVault"]
|
indro/client.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import uuid
|
|
5
|
+
import ctypes
|
|
6
|
+
import hashlib
|
|
7
|
+
import hmac
|
|
8
|
+
import platform
|
|
9
|
+
import asyncio
|
|
10
|
+
import aiohttp
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Tuple, List, Dict
|
|
13
|
+
|
|
14
|
+
from cryptography.hazmat.primitives.asymmetric import x25519
|
|
15
|
+
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
|
16
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
17
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from tqdm.asyncio import tqdm
|
|
21
|
+
except ImportError:
|
|
22
|
+
print("FATAL: Please install tqdm -> `pip install tqdm`")
|
|
23
|
+
sys.exit(1)
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from huggingface_hub import HfApi
|
|
27
|
+
HF_HUB_AVAILABLE = True
|
|
28
|
+
except ImportError:
|
|
29
|
+
HF_HUB_AVAILABLE = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ==========================================
|
|
33
|
+
# 1. HARDWARE ATTESTATION
|
|
34
|
+
# ==========================================
|
|
35
|
+
class VanguardAttestation:
|
|
36
|
+
@staticmethod
|
|
37
|
+
def get_device_fingerprint() -> str:
|
|
38
|
+
system_info = f"{platform.node()}_{platform.machine()}_{platform.system()}_{platform.processor()}"
|
|
39
|
+
return hashlib.sha256(system_info.encode('utf-8')).hexdigest()
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def get_binary_proof() -> str:
|
|
43
|
+
try:
|
|
44
|
+
with open(os.path.abspath(__file__), "rb") as f:
|
|
45
|
+
return hashlib.sha512(f.read()).hexdigest()
|
|
46
|
+
except Exception:
|
|
47
|
+
return hashlib.sha512(b"vanguard_secure_sdk_v16").hexdigest()
|
|
48
|
+
|
|
49
|
+
# ==========================================
|
|
50
|
+
# 2. CRYPTOGRAPHIC KERNEL
|
|
51
|
+
# ==========================================
|
|
52
|
+
class VanguardCryptoKernel:
|
|
53
|
+
def __init__(self):
|
|
54
|
+
self.client_priv = x25519.X25519PrivateKey.generate()
|
|
55
|
+
self.master_key: bytes = b""
|
|
56
|
+
self.CHUNKS_PER_KEY_GROUP = 256
|
|
57
|
+
self.CHUNK_SIZE = 1024 * 1024 * 2 # 2MB
|
|
58
|
+
|
|
59
|
+
def get_public_hex(self) -> str:
|
|
60
|
+
return self.client_priv.public_key().public_bytes(
|
|
61
|
+
serialization.Encoding.Raw, serialization.PublicFormat.Raw
|
|
62
|
+
).hex()
|
|
63
|
+
|
|
64
|
+
def derive_master_secret(self, server_pub_hex: str):
|
|
65
|
+
server_pub_bytes = bytes.fromhex(server_pub_hex)
|
|
66
|
+
server_pub_key = x25519.X25519PublicKey.from_public_bytes(server_pub_bytes)
|
|
67
|
+
shared_secret = self.client_priv.exchange(server_pub_key)
|
|
68
|
+
|
|
69
|
+
self.master_key = HKDF(algorithm=hashes.SHA256(), length=32, salt=None, info=b'indro-stream').derive(shared_secret)
|
|
70
|
+
self._shred_memory(shared_secret)
|
|
71
|
+
|
|
72
|
+
def derive_rotating_subkey(self, chunk_index: int) -> AESGCM:
|
|
73
|
+
chunk_group = chunk_index // self.CHUNKS_PER_KEY_GROUP
|
|
74
|
+
subkey = HKDF(algorithm=hashes.SHA256(), length=32, salt=None, info=f"vanguard-subkey-{chunk_group}".encode()).derive(self.master_key)
|
|
75
|
+
return AESGCM(subkey)
|
|
76
|
+
|
|
77
|
+
def verify_chunk_signature(self, chunk_index: int, session_id: str, provided_sig: bytes):
|
|
78
|
+
expected_sig = hmac.new(self.master_key, f"{session_id}:{chunk_index}".encode(), hashlib.sha256).digest()[:16]
|
|
79
|
+
if not hmac.compare_digest(provided_sig, expected_sig):
|
|
80
|
+
raise RuntimeError(f"CRITICAL: Chunk {chunk_index} signature verification failed! Network MitM attack detected.")
|
|
81
|
+
|
|
82
|
+
def shred_master_key(self):
|
|
83
|
+
self._shred_memory(self.master_key)
|
|
84
|
+
self.master_key = b""
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def _shred_memory(data: bytes):
|
|
88
|
+
try:
|
|
89
|
+
buffer = (ctypes.c_char * len(data)).from_buffer_copy(data)
|
|
90
|
+
ctypes.memset(ctypes.addressof(buffer), 0, len(data))
|
|
91
|
+
except Exception:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
# ==========================================
|
|
95
|
+
# 3. VANGUARD ENTERPRISE SDK
|
|
96
|
+
# ==========================================
|
|
97
|
+
class IndroVault:
|
|
98
|
+
def __init__(self, api_key: str = None, gateway_url: str = "https://abhinav337463-indro-veda-vanguard.hf.space"):
|
|
99
|
+
# The SDK automatically looks for the key in the user's computer!
|
|
100
|
+
self.api_key = api_key or os.getenv("VANGUARD_API_KEY")
|
|
101
|
+
|
|
102
|
+
if not self.api_key:
|
|
103
|
+
raise ValueError("Vanguard API Key missing. Set the 'VANGUARD_API_KEY' environment variable.")
|
|
104
|
+
|
|
105
|
+
self.gateway_url = gateway_url.rstrip("/")
|
|
106
|
+
self.fingerprint = VanguardAttestation.get_device_fingerprint()
|
|
107
|
+
|
|
108
|
+
self.headers = {
|
|
109
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
110
|
+
"X-Device-Fingerprint": self.fingerprint,
|
|
111
|
+
"X-Attestation-Proof": VanguardAttestation.get_binary_proof(),
|
|
112
|
+
"Content-Type": "application/json"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# --- FEATURE 1: SECURE DOWNLOAD ---
|
|
116
|
+
async def download_model_async(self, model_id: str, output_dir: str = "./models"):
|
|
117
|
+
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
|
118
|
+
output_path = Path(output_dir) / f"{model_id}.safetensors"
|
|
119
|
+
crypto = VanguardCryptoKernel()
|
|
120
|
+
|
|
121
|
+
print(f"\n[Vanguard] 🛡️ Initiating Zero-Trust Handshake for '{model_id}'...")
|
|
122
|
+
|
|
123
|
+
timestamp = int(time.time())
|
|
124
|
+
nonce = uuid.uuid4().hex
|
|
125
|
+
pub_hex = crypto.get_public_hex()
|
|
126
|
+
|
|
127
|
+
req_sig_payload = f"{timestamp}|{nonce}|{pub_hex}"
|
|
128
|
+
request_signature = hmac.new(self.api_key.encode(), req_sig_payload.encode(), hashlib.sha256).hexdigest()
|
|
129
|
+
|
|
130
|
+
payload = {"timestamp": timestamp, "nonce": nonce, "client_public_key_hex": pub_hex, "request_signature": request_signature}
|
|
131
|
+
|
|
132
|
+
async with aiohttp.ClientSession(headers=self.headers) as session:
|
|
133
|
+
async with session.post(f"{self.gateway_url}/api/v6/handshake/{model_id}", json=payload) as res:
|
|
134
|
+
if res.status != 200:
|
|
135
|
+
print(f"[Vanguard] ❌ Handshake Rejected: {await res.text()}")
|
|
136
|
+
sys.exit(1)
|
|
137
|
+
|
|
138
|
+
meta = await res.json()
|
|
139
|
+
session_token = meta['session_token']
|
|
140
|
+
crypto.derive_master_secret(meta['server_public_key_hex'])
|
|
141
|
+
nonce_prefix = bytes.fromhex(meta['nonce_prefix_hex'])
|
|
142
|
+
expected_sha256 = meta['sha256_hash']
|
|
143
|
+
session_id = session_token.split("|")[0]
|
|
144
|
+
|
|
145
|
+
print(f"[Vanguard] 🔐 Tunnel Secured. Pumping Decrypted Stream...")
|
|
146
|
+
stream_url = f"{self.gateway_url}/api/v6/stream/{session_token}"
|
|
147
|
+
integrity_hash = hashlib.sha256()
|
|
148
|
+
current_offset = 0
|
|
149
|
+
|
|
150
|
+
async with aiohttp.ClientSession(headers=self.headers) as session:
|
|
151
|
+
async with session.get(stream_url) as stream_res:
|
|
152
|
+
if stream_res.status not in (200, 206):
|
|
153
|
+
print(f"[Vanguard] ❌ Data Plane Failed: {stream_res.status}")
|
|
154
|
+
sys.exit(1)
|
|
155
|
+
|
|
156
|
+
total_size = int(stream_res.headers.get("Content-Length", 0))
|
|
157
|
+
progress = tqdm(total=total_size, unit='iB', unit_scale=True, bar_format="{l_bar}{bar:40}{r_bar}")
|
|
158
|
+
|
|
159
|
+
with open(output_path, "wb") as f:
|
|
160
|
+
while True:
|
|
161
|
+
length_bytes = await stream_res.content.read(4)
|
|
162
|
+
if not length_bytes: break
|
|
163
|
+
payload_len = int.from_bytes(length_bytes, 'big')
|
|
164
|
+
|
|
165
|
+
full_payload = await stream_res.content.read(payload_len)
|
|
166
|
+
if not full_payload: break
|
|
167
|
+
|
|
168
|
+
chunk_sig = full_payload[:16]
|
|
169
|
+
encrypted_chunk = full_payload[16:]
|
|
170
|
+
chunk_index = current_offset // crypto.CHUNK_SIZE
|
|
171
|
+
|
|
172
|
+
crypto.verify_chunk_signature(chunk_index, session_id, chunk_sig)
|
|
173
|
+
aesgcm = crypto.derive_rotating_subkey(chunk_index)
|
|
174
|
+
nonce = nonce_prefix + chunk_index.to_bytes(8, byteorder='big')
|
|
175
|
+
aad = f"{session_id}:{chunk_index}".encode('utf-8')
|
|
176
|
+
|
|
177
|
+
raw_chunk = aesgcm.decrypt(nonce, encrypted_chunk, aad)
|
|
178
|
+
|
|
179
|
+
if len(raw_chunk) == crypto.CHUNK_SIZE + 16:
|
|
180
|
+
raw_chunk = raw_chunk[:-16]
|
|
181
|
+
|
|
182
|
+
f.write(raw_chunk)
|
|
183
|
+
integrity_hash.update(raw_chunk)
|
|
184
|
+
|
|
185
|
+
chunk_size = len(raw_chunk)
|
|
186
|
+
current_offset += chunk_size
|
|
187
|
+
progress.update(len(full_payload) + 4)
|
|
188
|
+
|
|
189
|
+
progress.close()
|
|
190
|
+
|
|
191
|
+
final_hash = integrity_hash.hexdigest()
|
|
192
|
+
crypto.shred_master_key()
|
|
193
|
+
|
|
194
|
+
if expected_sha256 and final_hash != expected_sha256:
|
|
195
|
+
output_path.unlink()
|
|
196
|
+
print("\n[Vanguard] ❌ CRITICAL: Integrity Check Failed! File purged.")
|
|
197
|
+
sys.exit(1)
|
|
198
|
+
|
|
199
|
+
print(f"\n[Vanguard] ✅ Stream Complete! Secured at: {output_path.absolute()}")
|
|
200
|
+
return str(output_path.absolute())
|
|
201
|
+
|
|
202
|
+
def download_model(self, model_id: str, output_dir: str = "./models") -> str:
|
|
203
|
+
return str(asyncio.run(self.download_model_async(model_id, output_dir)))
|
|
204
|
+
|
|
205
|
+
# --- FEATURE 2: DIRECT-TO-CLOUD UPLOAD ---
|
|
206
|
+
async def upload_model_async(self, file_path: str, model_id: str, repo_id: str):
|
|
207
|
+
if not HF_HUB_AVAILABLE: raise RuntimeError("huggingface_hub is required for uploads. Run `pip install huggingface_hub`")
|
|
208
|
+
|
|
209
|
+
print(f"\n[Vanguard] 🚀 Requesting Upload Ticket for '{model_id}'...")
|
|
210
|
+
file_hash = hashlib.sha256()
|
|
211
|
+
with open(file_path, "rb") as f:
|
|
212
|
+
for byte_block in iter(lambda: f.read(4096),b""): file_hash.update(byte_block)
|
|
213
|
+
|
|
214
|
+
payload = {"model_id": model_id, "repo_id": repo_id, "target_file": Path(file_path).name}
|
|
215
|
+
|
|
216
|
+
async with aiohttp.ClientSession(headers=self.headers) as session:
|
|
217
|
+
async with session.post(f"{self.gateway_url}/api/v6/models/upload/ticket", json=payload) as res:
|
|
218
|
+
if res.status != 200:
|
|
219
|
+
print(f"[Vanguard] ❌ Upload Ticket Denied: {await res.text()}")
|
|
220
|
+
sys.exit(1)
|
|
221
|
+
ticket_data = await res.json()
|
|
222
|
+
|
|
223
|
+
print(f"[Vanguard] ☁️ Ticket Acquired. Streaming directly to Cloud Storage...")
|
|
224
|
+
api = HfApi(token=ticket_data["temp_token"])
|
|
225
|
+
api.upload_file(path_or_fileobj=file_path, path_in_repo=Path(file_path).name, repo_id=repo_id, repo_type="model")
|
|
226
|
+
|
|
227
|
+
confirm_payload = {"ticket_id": ticket_data["ticket_id"], "file_hash": file_hash.hexdigest()}
|
|
228
|
+
async with aiohttp.ClientSession(headers=self.headers) as session:
|
|
229
|
+
async with session.post(f"{self.gateway_url}/api/v6/models/upload/confirm", json=confirm_payload) as res:
|
|
230
|
+
if res.status != 200:
|
|
231
|
+
print(f"[Vanguard] ❌ Backend Registration Failed: {await res.text()}")
|
|
232
|
+
sys.exit(1)
|
|
233
|
+
print(f"[Vanguard] ✅ Upload Complete & Registered inside Vanguard Data Plane!")
|
|
234
|
+
|
|
235
|
+
def upload_model(self, file_path: str, model_id: str, repo_id: str):
|
|
236
|
+
asyncio.run(self.upload_model_async(file_path, model_id, repo_id))
|
|
237
|
+
|
|
238
|
+
# --- FEATURE 3: LIST MODELS ---
|
|
239
|
+
def list_models(self):
|
|
240
|
+
"""Fetches and prints all models registered to the user's workspace."""
|
|
241
|
+
async def fetch():
|
|
242
|
+
async with aiohttp.ClientSession(headers=self.headers) as session:
|
|
243
|
+
# We hit a mock endpoint here. You will need to add a simple GET /api/v6/models endpoint to your main.py!
|
|
244
|
+
async with session.get(f"{self.gateway_url}/api/v6/models") as res:
|
|
245
|
+
if res.status == 200:
|
|
246
|
+
models = await res.json()
|
|
247
|
+
print("\n[Vanguard] 📦 Registered Workspace Models:")
|
|
248
|
+
print("-" * 50)
|
|
249
|
+
for m in models:
|
|
250
|
+
size_gb = m.get('size_bytes', 0) / (1024**3)
|
|
251
|
+
print(f" ID: {m.get('model_id'):<20} | Repo: {m.get('repo_id'):<20} | Size: {size_gb:.2f} GB")
|
|
252
|
+
print("-" * 50)
|
|
253
|
+
else:
|
|
254
|
+
print(f"[Vanguard] ❌ Failed to fetch models: {await res.text()}")
|
|
255
|
+
asyncio.run(fetch())
|
|
256
|
+
|
|
257
|
+
# --- FEATURE 4: DELETE MODEL ---
|
|
258
|
+
def delete_model(self, model_id: str):
|
|
259
|
+
"""Terminates a model deployment."""
|
|
260
|
+
async def fetch():
|
|
261
|
+
async with aiohttp.ClientSession(headers=self.headers) as session:
|
|
262
|
+
async with session.delete(f"{self.gateway_url}/api/v6/models/{model_id}") as res:
|
|
263
|
+
if res.status in (200, 204):
|
|
264
|
+
print(f"\n[Vanguard] 🗑️ Model '{model_id}' successfully terminated from network.")
|
|
265
|
+
else:
|
|
266
|
+
print(f"\n[Vanguard] ❌ Failed to delete model: {await res.text()}")
|
|
267
|
+
asyncio.run(fetch())
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: indro
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Zero-Trust, Hyperscale AI Model Delivery Network SDK
|
|
5
|
+
Home-page: https://github.com/indrohelpdesk-cpu/indro-veda-sdk
|
|
6
|
+
Author: Abhinav Anand
|
|
7
|
+
Author-email: indrohelpdesk@gmail.com
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
|
+
Classifier: Topic :: Security :: Cryptography
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: aiohttp>=3.8.5
|
|
23
|
+
Requires-Dist: cryptography>=41.0.3
|
|
24
|
+
Requires-Dist: tqdm>=4.65.0
|
|
25
|
+
Requires-Dist: huggingface_hub>=0.19.0
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: home-page
|
|
32
|
+
Dynamic: requires-dist
|
|
33
|
+
Dynamic: requires-python
|
|
34
|
+
Dynamic: summary
|
|
35
|
+
|
|
36
|
+
# 🛡️ Indro Vanguard SDK
|
|
37
|
+
|
|
38
|
+
[](https://badge.fury.io/py/indro-veda)
|
|
39
|
+
[](https://www.python.org/downloads/)
|
|
40
|
+
[](https://opensource.org/licenses/MIT)
|
|
41
|
+
|
|
42
|
+
**The Official Enterprise Client for the Indro Vanguard Delivery Network.**
|
|
43
|
+
|
|
44
|
+
Indro-Veda is a military-grade, zero-trust data pipeline designed to securely stream and decrypt massive AI models and datasets on the fly. Built with hardcore cryptography and an asynchronous core, it allows developers to pull models without exposing API keys or passing unencrypted weights over the open internet.
|
|
45
|
+
|
|
46
|
+
## ✨ Enterprise Features
|
|
47
|
+
|
|
48
|
+
* **Zero-Trust Cryptography:** Utilizes local `X25519` keypair generation and ECDH key exchange to derive shared secrets. The server never transmits the decryption key over the network.
|
|
49
|
+
* **Military-Grade Encryption:** Streams are encrypted chunk-by-chunk using `AES-GCM` with dynamically rolling nonces.
|
|
50
|
+
* **Asynchronous Core:** Built on `aiohttp` to handle massive gigabyte streams concurrently without blocking your main thread.
|
|
51
|
+
* **Beautiful UX:** Integrated `tqdm` progress bars display real-time network speeds (MB/s) and ETAs directly in your terminal.
|
|
52
|
+
* **Bring Your Own Repo (BYOR):** Natively supports the Indro-Veda registry architecture, allowing developers to stream models hosted securely across the network.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 📦 Installation
|
|
57
|
+
|
|
58
|
+
Install the SDK directly from the Python Package Index (PyPI):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install indro-veda
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
indro/__init__.py,sha256=oFh3eU8OuXMeqVqScEtoP-nc2VOKHm6pntEbLmXilTw,303
|
|
2
|
+
indro/client.py,sha256=HnAhfyr6Zb8HYKgCg2E974wDiQC3pcCvUajKL87RFSI,12491
|
|
3
|
+
indro-0.0.1.dist-info/METADATA,sha256=FS0ap9YbWNbiXTxQiYFAmN041ACb6tchuqt6dFVdW6Q,2849
|
|
4
|
+
indro-0.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
5
|
+
indro-0.0.1.dist-info/top_level.txt,sha256=kG55QCD6Y8Id1sUU_DlS7r5NYhxrIBOMrcEfRIh4F8Y,6
|
|
6
|
+
indro-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
indro
|