zkai 0.5.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.
zkai-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: zkai
3
+ Version: 0.5.0
4
+ Summary: Private, verifiable AI inference on 0G chain — drop-in OpenAI-compatible SDK
5
+ Author-email: ZKai <team@zkai.network>
6
+ License: MIT
7
+ Project-URL: Homepage, https://zkai-ether-og.vercel.app
8
+ Project-URL: Repository, https://github.com/skyyycodes/zkai-eth
9
+ Project-URL: Issues, https://github.com/skyyycodes/zkai-eth/issues
10
+ Keywords: ai,inference,openai,llm,tee,tdx,attestation,blockchain,0g,verifiable,privacy,encryption
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Security :: Cryptography
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: cryptography>=43.0
24
+ Requires-Dist: requests>=2.32
25
+ Requires-Dist: pydantic>=2.9
26
+ Provides-Extra: langchain
27
+ Requires-Dist: langchain-core>=0.3; extra == "langchain"
28
+
29
+ # zkai
30
+
31
+ OpenAI-compatible Python SDK for [ZKai](https://zkai-ether-og.vercel.app) — private, verifiable AI inference on 0G chain.
32
+
33
+ ZKai sends your prompt through a Trusted Execution Environment (Intel TDX), encrypts it client-side so even the gateway cannot read it, and anchors a SHA-256 attestation of every inference on the 0G chain. You get back a normal OpenAI-style response plus an on-chain receipt.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install zkai
39
+ ```
40
+
41
+ Optional LangChain adapter:
42
+
43
+ ```bash
44
+ pip install "zkai[langchain]"
45
+ ```
46
+
47
+ ## Quick start
48
+
49
+ ```python
50
+ from zkai import ZKai
51
+
52
+ client = ZKai(api_key="zkai-...") # get one at https://zkai-ether-og.vercel.app
53
+
54
+ response = client.chat.completions.create(
55
+ model="qwen2.5:1.5b",
56
+ messages=[{"role": "user", "content": "Hello!"}],
57
+ )
58
+
59
+ print(response.choices[0].message.content)
60
+ print("Attestation hash:", response.attestation_hash)
61
+ ```
62
+
63
+ The response is decrypted locally — the gateway sees only ciphertext.
64
+ The attestation hash is anchored on 0G mainnet and can be verified independently.
65
+
66
+ ## How it works (gateway mode, default)
67
+
68
+ 1. The SDK fetches the enclave's X25519 public key from the gateway.
69
+ 2. It encrypts your prompt locally with ECDH + ChaCha20-Poly1305.
70
+ 3. It sends only the ciphertext to the gateway.
71
+ 4. The gateway routes the opaque blob to a TDX-sealed enclave running the requested model.
72
+ 5. The enclave decrypts inside sealed memory, runs the model, encrypts the response, and emits a SHA-256 attestation hash.
73
+ 6. The attestation lands on the on-chain `AttestationRegistry` contract.
74
+ 7. The SDK decrypts the response locally and returns it to you.
75
+
76
+ End-to-end, the gateway never sees plaintext.
77
+
78
+ ## Configuration
79
+
80
+ ```python
81
+ ZKai(
82
+ api_key="zkai-...", # issued from the dashboard
83
+ base_url="https://zkai-ether-og.vercel.app", # default
84
+ encrypted=True, # default; set False to use the legacy plaintext path
85
+ provider_endpoint=None, # set to bypass gateway and hit a provider directly
86
+ skip_attestation=False, # only for development; do not disable in prod
87
+ )
88
+ ```
89
+
90
+ | Argument | Type | Default | Description |
91
+ |---|---|---|---|
92
+ | `api_key` | `str | None` | `None` | Sent as `X-API-Key`. Required for the hosted gateway. |
93
+ | `base_url` | `str | None` | `https://zkai-ether-og.vercel.app` | Override to point at a self-hosted gateway. |
94
+ | `encrypted` | `bool` | `True` | When `True`, prompts are encrypted client-side. Set `False` for legacy clients. |
95
+ | `provider_endpoint` | `str | None` | `None` | When set, bypasses the gateway and talks directly to a provider's `/infer` endpoint. |
96
+ | `skip_attestation` | `bool` | `False` | Disables attestation verification. Useful only for dev loops. |
97
+
98
+ ## OpenAI compatibility
99
+
100
+ The response object mirrors OpenAI's chat completion shape:
101
+
102
+ ```python
103
+ response.id
104
+ response.model
105
+ response.choices[0].message.content
106
+ response.choices[0].finish_reason
107
+ response.usage.prompt_tokens
108
+ response.usage.completion_tokens
109
+
110
+ # ZKai-specific:
111
+ response.attestation_hash # on-chain commitment
112
+ ```
113
+
114
+ Drop-in replacement for most OpenAI SDK call sites — just swap the client class.
115
+
116
+ ## On-chain components (0G mainnet, chain ID 16661)
117
+
118
+ | Contract | Address |
119
+ |---|---|
120
+ | ProviderRegistry | `0x6D400F5D1DcCaA3e98E3dE17322aA23DE38bAC99` |
121
+ | PaymentEscrow | `0xb2C7c0F7a4C2877319E8Ed1Fae0bf3C705b6Fc4C` |
122
+ | AttestationRegistry | `0x8c8Ae0A113084268D181fd1cf23d611DC2EAa2B2` |
123
+
124
+ Verify any attestation hash on the [0G explorer](https://chainscan.0g.ai).
125
+
126
+ ## Running your own provider
127
+
128
+ See the companion [`zkai-cli`](https://pypi.org/project/zkai-cli/) package:
129
+
130
+ ```bash
131
+ pip install zkai-cli
132
+ zkai init && zkai start && zkai register
133
+ ```
134
+
135
+ ## Links
136
+
137
+ - Dashboard — https://zkai-ether-og.vercel.app
138
+ - Repository — https://github.com/skyyycodes/zkai-eth
139
+ - Provider CLI — https://pypi.org/project/zkai-cli/
140
+
141
+ ## License
142
+
143
+ MIT
zkai-0.5.0/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # zkai
2
+
3
+ OpenAI-compatible Python SDK for [ZKai](https://zkai-ether-og.vercel.app) — private, verifiable AI inference on 0G chain.
4
+
5
+ ZKai sends your prompt through a Trusted Execution Environment (Intel TDX), encrypts it client-side so even the gateway cannot read it, and anchors a SHA-256 attestation of every inference on the 0G chain. You get back a normal OpenAI-style response plus an on-chain receipt.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install zkai
11
+ ```
12
+
13
+ Optional LangChain adapter:
14
+
15
+ ```bash
16
+ pip install "zkai[langchain]"
17
+ ```
18
+
19
+ ## Quick start
20
+
21
+ ```python
22
+ from zkai import ZKai
23
+
24
+ client = ZKai(api_key="zkai-...") # get one at https://zkai-ether-og.vercel.app
25
+
26
+ response = client.chat.completions.create(
27
+ model="qwen2.5:1.5b",
28
+ messages=[{"role": "user", "content": "Hello!"}],
29
+ )
30
+
31
+ print(response.choices[0].message.content)
32
+ print("Attestation hash:", response.attestation_hash)
33
+ ```
34
+
35
+ The response is decrypted locally — the gateway sees only ciphertext.
36
+ The attestation hash is anchored on 0G mainnet and can be verified independently.
37
+
38
+ ## How it works (gateway mode, default)
39
+
40
+ 1. The SDK fetches the enclave's X25519 public key from the gateway.
41
+ 2. It encrypts your prompt locally with ECDH + ChaCha20-Poly1305.
42
+ 3. It sends only the ciphertext to the gateway.
43
+ 4. The gateway routes the opaque blob to a TDX-sealed enclave running the requested model.
44
+ 5. The enclave decrypts inside sealed memory, runs the model, encrypts the response, and emits a SHA-256 attestation hash.
45
+ 6. The attestation lands on the on-chain `AttestationRegistry` contract.
46
+ 7. The SDK decrypts the response locally and returns it to you.
47
+
48
+ End-to-end, the gateway never sees plaintext.
49
+
50
+ ## Configuration
51
+
52
+ ```python
53
+ ZKai(
54
+ api_key="zkai-...", # issued from the dashboard
55
+ base_url="https://zkai-ether-og.vercel.app", # default
56
+ encrypted=True, # default; set False to use the legacy plaintext path
57
+ provider_endpoint=None, # set to bypass gateway and hit a provider directly
58
+ skip_attestation=False, # only for development; do not disable in prod
59
+ )
60
+ ```
61
+
62
+ | Argument | Type | Default | Description |
63
+ |---|---|---|---|
64
+ | `api_key` | `str | None` | `None` | Sent as `X-API-Key`. Required for the hosted gateway. |
65
+ | `base_url` | `str | None` | `https://zkai-ether-og.vercel.app` | Override to point at a self-hosted gateway. |
66
+ | `encrypted` | `bool` | `True` | When `True`, prompts are encrypted client-side. Set `False` for legacy clients. |
67
+ | `provider_endpoint` | `str | None` | `None` | When set, bypasses the gateway and talks directly to a provider's `/infer` endpoint. |
68
+ | `skip_attestation` | `bool` | `False` | Disables attestation verification. Useful only for dev loops. |
69
+
70
+ ## OpenAI compatibility
71
+
72
+ The response object mirrors OpenAI's chat completion shape:
73
+
74
+ ```python
75
+ response.id
76
+ response.model
77
+ response.choices[0].message.content
78
+ response.choices[0].finish_reason
79
+ response.usage.prompt_tokens
80
+ response.usage.completion_tokens
81
+
82
+ # ZKai-specific:
83
+ response.attestation_hash # on-chain commitment
84
+ ```
85
+
86
+ Drop-in replacement for most OpenAI SDK call sites — just swap the client class.
87
+
88
+ ## On-chain components (0G mainnet, chain ID 16661)
89
+
90
+ | Contract | Address |
91
+ |---|---|
92
+ | ProviderRegistry | `0x6D400F5D1DcCaA3e98E3dE17322aA23DE38bAC99` |
93
+ | PaymentEscrow | `0xb2C7c0F7a4C2877319E8Ed1Fae0bf3C705b6Fc4C` |
94
+ | AttestationRegistry | `0x8c8Ae0A113084268D181fd1cf23d611DC2EAa2B2` |
95
+
96
+ Verify any attestation hash on the [0G explorer](https://chainscan.0g.ai).
97
+
98
+ ## Running your own provider
99
+
100
+ See the companion [`zkai-cli`](https://pypi.org/project/zkai-cli/) package:
101
+
102
+ ```bash
103
+ pip install zkai-cli
104
+ zkai init && zkai start && zkai register
105
+ ```
106
+
107
+ ## Links
108
+
109
+ - Dashboard — https://zkai-ether-og.vercel.app
110
+ - Repository — https://github.com/skyyycodes/zkai-eth
111
+ - Provider CLI — https://pypi.org/project/zkai-cli/
112
+
113
+ ## License
114
+
115
+ MIT
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zkai"
7
+ version = "0.5.0"
8
+ description = "Private, verifiable AI inference on 0G chain — drop-in OpenAI-compatible SDK"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "ZKai", email = "team@zkai.network" },
14
+ ]
15
+ keywords = [
16
+ "ai", "inference", "openai", "llm", "tee", "tdx", "attestation",
17
+ "blockchain", "0g", "verifiable", "privacy", "encryption",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Intended Audience :: Developers",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Operating System :: OS Independent",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
29
+ "Topic :: Security :: Cryptography",
30
+ ]
31
+ dependencies = [
32
+ "cryptography>=43.0",
33
+ "requests>=2.32",
34
+ "pydantic>=2.9",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ langchain = [
39
+ "langchain-core>=0.3",
40
+ ]
41
+
42
+ [project.urls]
43
+ Homepage = "https://zkai-ether-og.vercel.app"
44
+ Repository = "https://github.com/skyyycodes/zkai-eth"
45
+ Issues = "https://github.com/skyyycodes/zkai-eth/issues"
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["."]
49
+ include = ["zkai*"]
50
+ exclude = ["zkai.egg-info*"]
zkai-0.5.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,10 @@
1
+ from .client import ZKai, ChatCompletion, ZKaiAuthError
2
+ from .attestation import ZKaiAttestationError
3
+
4
+ def __getattr__(name):
5
+ if name == "ChatZKai":
6
+ from .langchain import ChatZKai
7
+ return ChatZKai
8
+ raise AttributeError(f"module 'zkai' has no attribute {name!r}")
9
+
10
+ __all__ = ["ZKai", "ChatCompletion", "ZKaiAuthError", "ZKaiAttestationError", "ChatZKai"]
@@ -0,0 +1,102 @@
1
+ """
2
+ Attestation verification.
3
+ SDK calls this silently after every inference — user never thinks about it.
4
+ """
5
+
6
+ import hashlib
7
+ import json
8
+ import requests
9
+
10
+ INDEXER_URL = "https://indexer.preprod.midnight.network/api/v3/graphql"
11
+
12
+
13
+ class ZKaiAttestationError(Exception):
14
+ pass
15
+
16
+
17
+ def verify(
18
+ provider_url: str,
19
+ received_attestation_hash: str,
20
+ on_chain_hash: str | None = None,
21
+ attestation_contract: str | None = None,
22
+ job_id: str | None = None,
23
+ ):
24
+ """
25
+ Fetch attestation from provider, hash it, compare to:
26
+ 1. The hash included in the /infer response
27
+ 2. The hash anchored on-chain (if attestation_contract + job_id provided)
28
+
29
+ Raises ZKaiAttestationError if anything doesn't match.
30
+ """
31
+ resp = requests.get(f"{provider_url}/attestation", timeout=10)
32
+ resp.raise_for_status()
33
+ attestation = resp.json()
34
+
35
+ # Recompute hash of the report (excluding the hash field itself)
36
+ report_copy = {k: v for k, v in attestation.items() if k not in ("report_hash", "signature")}
37
+ report_bytes = json.dumps(report_copy, sort_keys=True).encode()
38
+ computed_hash = hashlib.sha256(report_bytes).hexdigest()
39
+
40
+ if computed_hash != received_attestation_hash:
41
+ raise ZKaiAttestationError(
42
+ f"Attestation hash mismatch.\n"
43
+ f" From provider response: {received_attestation_hash}\n"
44
+ f" Recomputed: {computed_hash}\n"
45
+ f" Provider may have tampered with the report."
46
+ )
47
+
48
+ # Fetch on-chain hash if not provided directly
49
+ if on_chain_hash is None and attestation_contract and job_id:
50
+ on_chain_hash = _fetch_on_chain_hash(attestation_contract, job_id)
51
+
52
+ if on_chain_hash and computed_hash != on_chain_hash:
53
+ raise ZKaiAttestationError(
54
+ f"Attestation does not match on-chain anchor.\n"
55
+ f" On-chain: {on_chain_hash}\n"
56
+ f" Computed: {computed_hash}\n"
57
+ f" Model hash: {attestation.get('model_hash', 'unknown')}\n"
58
+ f" Possible tampered model or manifest."
59
+ )
60
+
61
+ return attestation
62
+
63
+
64
+ def _fetch_on_chain_hash(attestation_contract: str, job_id: str) -> str | None:
65
+ """Query Midnight indexer for the attestation hash stored for a given job_id."""
66
+ query = """
67
+ query GetAttestation($address: String!) {
68
+ contract(address: $address) {
69
+ state {
70
+ ... on ContractState {
71
+ ledger {
72
+ ... on ZkaiAttestationRegistryLedger {
73
+ att_hash { entries { key value } }
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ """
81
+ try:
82
+ resp = requests.post(
83
+ INDEXER_URL,
84
+ json={"query": query, "variables": {"address": attestation_contract}},
85
+ timeout=15,
86
+ )
87
+ resp.raise_for_status()
88
+ data = resp.json()
89
+ entries = (
90
+ data.get("data", {})
91
+ .get("contract", {})
92
+ .get("state", {})
93
+ .get("ledger", {})
94
+ .get("att_hash", {})
95
+ .get("entries", [])
96
+ )
97
+ for entry in entries:
98
+ if entry["key"] == job_id:
99
+ return entry["value"]
100
+ return None
101
+ except Exception:
102
+ return None
@@ -0,0 +1,28 @@
1
+ """
2
+ HTTP client for the ZKai bridge server (Node.js).
3
+ The bridge holds the Midnight wallet and translates Python calls into on-chain transactions.
4
+ """
5
+
6
+ import requests
7
+
8
+
9
+ class BridgeClient:
10
+ def __init__(self, base_url: str = "http://localhost:7300"):
11
+ self.base_url = base_url.rstrip("/")
12
+
13
+ def call(self, path: str, payload: dict) -> dict:
14
+ resp = requests.post(
15
+ f"{self.base_url}{path}",
16
+ json=payload,
17
+ timeout=120, # ZK proofs can take 30-60s
18
+ )
19
+ resp.raise_for_status()
20
+ data = resp.json()
21
+ if "error" in data:
22
+ raise RuntimeError(f"Bridge error on {path}: {data['error']}")
23
+ return data
24
+
25
+ def health(self) -> dict:
26
+ resp = requests.get(f"{self.base_url}/health", timeout=10)
27
+ resp.raise_for_status()
28
+ return resp.json()
@@ -0,0 +1,286 @@
1
+ """
2
+ ZKai client — OpenAI-compatible interface with end-to-end encryption.
3
+
4
+ Modes:
5
+ - Encrypted gateway (default): prompt encrypted client-side with the
6
+ enclave's X25519 pubkey; gateway sees only ciphertext.
7
+ - Plain gateway: legacy fallback for compatibility.
8
+ - Direct provider: bypass gateway entirely.
9
+
10
+ Change 2 lines, everything else stays the same.
11
+ """
12
+
13
+ import requests
14
+ from dataclasses import dataclass, field
15
+
16
+ from . import crypto, provider as provider_mod, attestation as att_mod
17
+ from .attestation import ZKaiAttestationError
18
+
19
+ # Default gateway — consumers send requests here, gateway picks a provider
20
+ GATEWAY_URL = "https://zkai-ether-og.vercel.app"
21
+
22
+
23
+ # ── OpenAI-compatible response types ────────────────────────────────────────
24
+
25
+ @dataclass
26
+ class Message:
27
+ role: str
28
+ content: str
29
+
30
+
31
+ @dataclass
32
+ class Choice:
33
+ index: int
34
+ message: Message
35
+ finish_reason: str = "stop"
36
+
37
+
38
+ @dataclass
39
+ class ChatCompletion:
40
+ id: str
41
+ object: str
42
+ model: str
43
+ choices: list[Choice]
44
+ usage: dict = field(default_factory=dict)
45
+ attestation_hash: str | None = None
46
+
47
+
48
+ # ── Main client ──────────────────────────────────────────────────────────────
49
+
50
+ class ZKai:
51
+ def __init__(
52
+ self,
53
+ api_key: str | None = None,
54
+ base_url: str | None = None,
55
+ # legacy / advanced: bypass gateway and talk to a provider directly
56
+ provider_endpoint: str | None = None,
57
+ max_price: float | None = None,
58
+ min_reputation: float = 0.0,
59
+ registry_contract: str | None = None,
60
+ attestation_contract: str | None = None,
61
+ skip_attestation: bool = False,
62
+ # End-to-end encryption via gateway (default ON). Set False to use
63
+ # the legacy plaintext-to-gateway path.
64
+ encrypted: bool = True,
65
+ ):
66
+ self._api_key = api_key
67
+ self._base_url = (base_url or GATEWAY_URL).rstrip("/")
68
+ self._provider_endpoint = provider_endpoint
69
+ self._max_price = max_price
70
+ self._min_reputation = min_reputation
71
+ self._registry_contract = registry_contract
72
+ self._attestation_contract = attestation_contract
73
+ self._skip_attestation = skip_attestation
74
+ self._encrypted = encrypted
75
+ self.chat = _Chat(self)
76
+
77
+ def _infer(self, model: str, messages: list[dict]) -> ChatCompletion:
78
+ if self._provider_endpoint:
79
+ return self._infer_direct(model, messages)
80
+ if self._encrypted:
81
+ return self._infer_via_gateway_encrypted(model, messages)
82
+ return self._infer_via_gateway_plain(model, messages)
83
+
84
+ # ── Encrypted gateway path (default) ─────────────────────────────────────
85
+
86
+ def _infer_via_gateway_encrypted(self, model: str, messages: list[dict]) -> ChatCompletion:
87
+ """
88
+ End-to-end encrypted via the ZKai gateway.
89
+
90
+ Flow:
91
+ 1. GET /api/providers/pubkey?model=... → enclave pubkey + provider_id
92
+ 2. Generate ephemeral X25519 keypair locally
93
+ 3. Encrypt prompt with ECDH(ephemeral_priv, enclave_pub) → ciphertext
94
+ 4. POST /api/v1/encrypted-chat with { provider_id, client_pubkey, encrypted_prompt }
95
+ 5. Decrypt response with ECDH(ephemeral_priv, enclave_pub)
96
+
97
+ Gateway never sees plaintext.
98
+ """
99
+ headers = {"Content-Type": "application/json"}
100
+ if self._api_key:
101
+ headers["X-API-Key"] = self._api_key
102
+
103
+ # 1. Fetch enclave pubkey for this model
104
+ pk_resp = requests.get(
105
+ f"{self._base_url}/api/providers/pubkey",
106
+ params={"model": model},
107
+ headers=headers,
108
+ timeout=15,
109
+ )
110
+ if pk_resp.status_code == 503:
111
+ raise ZKaiNoProviderError("No providers available for this model.")
112
+ pk_resp.raise_for_status()
113
+ pk_data = pk_resp.json()
114
+ enclave_pubkey = pk_data["pubkey"]
115
+ provider_id = pk_data["provider_id"]
116
+
117
+ # 2-3. Generate ephemeral keypair + encrypt
118
+ prompt = _messages_to_prompt(messages)
119
+ our_priv, our_pub = crypto.generate_keypair()
120
+ encrypted_prompt = crypto.encrypt(prompt, enclave_pubkey, our_priv)
121
+
122
+ # 4. Send encrypted blob via gateway
123
+ resp = requests.post(
124
+ f"{self._base_url}/api/v1/encrypted-chat",
125
+ json={
126
+ "provider_id": provider_id,
127
+ "client_pubkey": our_pub,
128
+ "encrypted_prompt": encrypted_prompt,
129
+ "model": model,
130
+ },
131
+ headers=headers,
132
+ timeout=120,
133
+ )
134
+ if resp.status_code == 401:
135
+ raise ZKaiAuthError("Invalid or missing API key. Get one at https://zkai-ether-og.vercel.app")
136
+ if resp.status_code == 503:
137
+ raise ZKaiNoProviderError("No providers available for this model.")
138
+ resp.raise_for_status()
139
+ data = resp.json()
140
+
141
+ # 5. Decrypt response locally
142
+ response_text = crypto.decrypt(data["encrypted_response"], enclave_pubkey, our_priv)
143
+ token_count = len(response_text.split())
144
+
145
+ return ChatCompletion(
146
+ id=f"zkai-{data['job_id'][:8]}",
147
+ object="chat.completion",
148
+ model=model,
149
+ choices=[Choice(
150
+ index=0,
151
+ message=Message(role="assistant", content=response_text),
152
+ finish_reason="stop",
153
+ )],
154
+ usage={"prompt_tokens": len(prompt.split()), "completion_tokens": token_count},
155
+ attestation_hash=data.get("attestation_hash"),
156
+ )
157
+
158
+ # ── Plain gateway path (legacy) ──────────────────────────────────────────
159
+
160
+ def _infer_via_gateway_plain(self, model: str, messages: list[dict]) -> ChatCompletion:
161
+ """Send plaintext request to the ZKai gateway (legacy)."""
162
+ headers = {"Content-Type": "application/json"}
163
+ if self._api_key:
164
+ headers["X-API-Key"] = self._api_key
165
+
166
+ resp = requests.post(
167
+ f"{self._base_url}/api/v1/chat/completions",
168
+ json={"model": model, "messages": messages},
169
+ headers=headers,
170
+ timeout=120,
171
+ )
172
+ if resp.status_code == 401:
173
+ raise ZKaiAuthError("Invalid or missing API key.")
174
+ if resp.status_code == 503:
175
+ raise ZKaiNoProviderError("No providers available for this model.")
176
+ resp.raise_for_status()
177
+ data = resp.json()
178
+
179
+ choice = data["choices"][0]
180
+ xz = data.get("x_zkai", {})
181
+ return ChatCompletion(
182
+ id=data.get("id", "zkai-gateway"),
183
+ object="chat.completion",
184
+ model=data.get("model", model),
185
+ choices=[Choice(
186
+ index=0,
187
+ message=Message(role="assistant", content=choice["message"]["content"]),
188
+ finish_reason=choice.get("finish_reason", "stop"),
189
+ )],
190
+ usage=data.get("usage", {}),
191
+ attestation_hash=xz.get("attestation_hash"),
192
+ )
193
+
194
+ # ── Direct provider path (advanced) ──────────────────────────────────────
195
+
196
+ def _infer_direct(self, model: str, messages: list[dict]) -> ChatCompletion:
197
+ """Bypass gateway — encrypt and send directly to a provider enclave."""
198
+ prompt = _messages_to_prompt(messages)
199
+
200
+ p = provider_mod.Provider(
201
+ id="direct",
202
+ endpoint=self._provider_endpoint,
203
+ pubkey="",
204
+ model=model,
205
+ price_per_token=0.0,
206
+ reputation=1.0,
207
+ stake=0.0,
208
+ )
209
+
210
+ tee_pubkey = provider_mod.fetch_pubkey(p)
211
+ our_private, our_public = crypto.generate_keypair()
212
+ encrypted_prompt = crypto.encrypt(prompt, tee_pubkey, our_private)
213
+
214
+ headers = {"X-API-Key": self._api_key} if self._api_key else {}
215
+ resp = requests.post(
216
+ f"{p.endpoint}/infer",
217
+ json={"client_pubkey": our_public, "encrypted_prompt": encrypted_prompt},
218
+ headers=headers,
219
+ timeout=120,
220
+ )
221
+ if resp.status_code == 401:
222
+ raise ZKaiAuthError("Invalid or missing API key.")
223
+ resp.raise_for_status()
224
+ data = resp.json()
225
+
226
+ job_id = data["job_id"]
227
+
228
+ if not self._skip_attestation:
229
+ att_mod.verify(
230
+ provider_url=p.endpoint,
231
+ received_attestation_hash=data["attestation_hash"],
232
+ attestation_contract=self._attestation_contract,
233
+ job_id=job_id,
234
+ )
235
+
236
+ response_text = crypto.decrypt(data["encrypted_response"], tee_pubkey, our_private)
237
+ token_count = len(response_text.split())
238
+
239
+ return ChatCompletion(
240
+ id=f"zkai-{job_id[:8]}",
241
+ object="chat.completion",
242
+ model=model,
243
+ choices=[Choice(index=0, message=Message(role="assistant", content=response_text))],
244
+ usage={"prompt_tokens": len(prompt.split()), "completion_tokens": token_count},
245
+ attestation_hash=data.get("attestation_hash"),
246
+ )
247
+
248
+
249
+ class _Chat:
250
+ def __init__(self, client: ZKai):
251
+ self.completions = _Completions(client)
252
+
253
+
254
+ class _Completions:
255
+ def __init__(self, client: ZKai):
256
+ self._client = client
257
+
258
+ def create(self, model: str, messages: list[dict], **kwargs) -> ChatCompletion:
259
+ return self._client._infer(model, messages)
260
+
261
+
262
+ # ── Exceptions ───────────────────────────────────────────────────────────────
263
+
264
+ class ZKaiAuthError(Exception):
265
+ pass
266
+
267
+
268
+ class ZKaiNoProviderError(Exception):
269
+ pass
270
+
271
+
272
+ # ── Helpers ──────────────────────────────────────────────────────────────────
273
+
274
+ def _messages_to_prompt(messages: list[dict]) -> str:
275
+ parts = []
276
+ for m in messages:
277
+ role = m.get("role", "user")
278
+ content = m.get("content", "")
279
+ if role == "system":
280
+ parts.append(f"System: {content}")
281
+ elif role == "user":
282
+ parts.append(f"User: {content}")
283
+ elif role == "assistant":
284
+ parts.append(f"Assistant: {content}")
285
+ parts.append("Assistant:")
286
+ return "\n".join(parts)
@@ -0,0 +1,60 @@
1
+ """
2
+ Client-side encryption primitives.
3
+ Matches the enclave's decrypt logic exactly.
4
+ """
5
+
6
+ import os
7
+ import hashlib
8
+
9
+ from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
10
+ from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat, PrivateFormat, NoEncryption
11
+ from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
12
+
13
+
14
+ def generate_keypair() -> tuple[str, str]:
15
+ """Generate ephemeral X25519 keypair. Returns (private_hex, public_hex)."""
16
+ private_key = X25519PrivateKey.generate()
17
+ private_bytes = private_key.private_bytes(Encoding.Raw, PrivateFormat.Raw, NoEncryption())
18
+ public_bytes = private_key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
19
+ return private_bytes.hex(), public_bytes.hex()
20
+
21
+
22
+ def encrypt(plaintext: str, recipient_pubkey_hex: str, our_private_hex: str) -> str:
23
+ """
24
+ ECDH + ChaCha20-Poly1305 encrypt.
25
+ Returns hex(nonce + ciphertext + tag).
26
+ """
27
+ recipient_pubkey = X25519PublicKey.from_public_bytes(bytes.fromhex(recipient_pubkey_hex))
28
+ our_private = X25519PrivateKey.from_private_bytes(bytes.fromhex(our_private_hex))
29
+
30
+ shared_secret = our_private.exchange(recipient_pubkey)
31
+ key = _derive_key(shared_secret)
32
+
33
+ nonce = os.urandom(12)
34
+ chacha = ChaCha20Poly1305(key)
35
+ ciphertext = chacha.encrypt(nonce, plaintext.encode("utf-8"), None)
36
+ return (nonce + ciphertext).hex()
37
+
38
+
39
+ def decrypt(encrypted_hex: str, sender_pubkey_hex: str, our_private_hex: str) -> str:
40
+ """
41
+ ECDH + ChaCha20-Poly1305 decrypt.
42
+ encrypted_hex = hex(nonce + ciphertext + tag)
43
+ """
44
+ sender_pubkey = X25519PublicKey.from_public_bytes(bytes.fromhex(sender_pubkey_hex))
45
+ our_private = X25519PrivateKey.from_private_bytes(bytes.fromhex(our_private_hex))
46
+
47
+ shared_secret = our_private.exchange(sender_pubkey)
48
+ key = _derive_key(shared_secret)
49
+
50
+ data = bytes.fromhex(encrypted_hex)
51
+ nonce = data[:12]
52
+ payload = data[12:]
53
+
54
+ chacha = ChaCha20Poly1305(key)
55
+ plaintext = chacha.decrypt(nonce, payload, None)
56
+ return plaintext.decode("utf-8")
57
+
58
+
59
+ def _derive_key(shared_secret: bytes) -> bytes:
60
+ return hashlib.sha256(shared_secret).digest()
@@ -0,0 +1,63 @@
1
+ """
2
+ LangChain adapter — one line change from ChatOpenAI.
3
+
4
+ Before:
5
+ from langchain_openai import ChatOpenAI
6
+ llm = ChatOpenAI(model="gpt-4")
7
+
8
+ After:
9
+ from zkai import ChatZKai
10
+ llm = ChatZKai(model="qwen2.5-1.5b", api_key="your-key")
11
+ """
12
+
13
+ from typing import Any, List, Optional
14
+ from langchain_core.language_models.chat_models import BaseChatModel
15
+ from langchain_core.messages import BaseMessage, AIMessage
16
+ from langchain_core.outputs import ChatGeneration, ChatResult
17
+
18
+ from .client import ZKai
19
+
20
+
21
+ class ChatZKai(BaseChatModel):
22
+ model: str = "qwen2.5-1.5b"
23
+ api_key: Optional[str] = None
24
+ max_price: Optional[float] = None
25
+ min_reputation: float = 0.0
26
+ registry_contract: Optional[str] = None
27
+ attestation_contract: Optional[str] = None
28
+ skip_attestation: bool = False
29
+
30
+ @property
31
+ def _llm_type(self) -> str:
32
+ return "zkai"
33
+
34
+ def _get_client(self) -> ZKai:
35
+ return ZKai(
36
+ api_key=self.api_key,
37
+ max_price=self.max_price,
38
+ min_reputation=self.min_reputation,
39
+ registry_contract=self.registry_contract,
40
+ attestation_contract=self.attestation_contract,
41
+ skip_attestation=self.skip_attestation,
42
+ )
43
+
44
+ def _generate(self, messages: List[BaseMessage], **kwargs: Any) -> ChatResult:
45
+ openai_messages = [
46
+ {"role": _lc_role(m), "content": m.content}
47
+ for m in messages
48
+ ]
49
+ completion = self._get_client().chat.completions.create(
50
+ model=self.model,
51
+ messages=openai_messages,
52
+ )
53
+ text = completion.choices[0].message.content
54
+ return ChatResult(generations=[ChatGeneration(message=AIMessage(content=text))])
55
+
56
+
57
+ def _lc_role(message: BaseMessage) -> str:
58
+ from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
59
+ if isinstance(message, SystemMessage):
60
+ return "system"
61
+ if isinstance(message, AIMessage):
62
+ return "assistant"
63
+ return "user"
@@ -0,0 +1,38 @@
1
+ """
2
+ Midnight payment escrow integration via ZKai bridge server.
3
+ """
4
+
5
+ from .bridge import BridgeClient
6
+
7
+
8
+ class PaymentClient:
9
+ def __init__(self, wallet_key: str | None = None, bridge_url: str | None = None):
10
+ self.wallet_key = wallet_key
11
+ self._bridge = BridgeClient(bridge_url) if bridge_url else None
12
+
13
+ def create_job(self, provider_id: str, token_budget: int, job_id: str) -> str:
14
+ """Lock DUST in escrow. Returns the same job_id."""
15
+ if self._bridge is None:
16
+ return job_id # local dev no-op
17
+
18
+ self._bridge.call("/payment/create-job", {
19
+ "job_id": job_id,
20
+ "provider_id": provider_id,
21
+ "amount": str(token_budget),
22
+ })
23
+ return job_id
24
+
25
+ def complete_job(self, job_id: str, attestation_hash: str, token_count: int):
26
+ """Release escrow payment to provider."""
27
+ if self._bridge is None:
28
+ return
29
+ self._bridge.call("/payment/complete-job", {
30
+ "job_id": job_id,
31
+ "attestation_hash": attestation_hash,
32
+ })
33
+
34
+ def dispute_job(self, job_id: str):
35
+ """Trigger refund — called automatically on attestation failure."""
36
+ if self._bridge is None:
37
+ return
38
+ self._bridge.call("/payment/dispute-job", {"job_id": job_id})
@@ -0,0 +1,142 @@
1
+ """
2
+ Provider discovery and selection.
3
+ - No registry_contract: local dev stub (localhost:8080)
4
+ - With registry_contract: queries Midnight indexer GraphQL for on-chain providers
5
+ """
6
+
7
+ import requests
8
+ from dataclasses import dataclass
9
+
10
+ INDEXER_URL = "https://indexer.preprod.midnight.network/api/v3/graphql"
11
+
12
+
13
+ @dataclass
14
+ class Provider:
15
+ id: str
16
+ endpoint: str
17
+ pubkey: str
18
+ model: str
19
+ price_per_token: float
20
+ reputation: float
21
+ stake: float
22
+
23
+
24
+ def select_provider(
25
+ model: str,
26
+ max_price: float | None = None,
27
+ min_reputation: float = 0.0,
28
+ registry_contract: str | None = None,
29
+ ) -> Provider:
30
+ """Pick the best available provider. Ranked by reputation desc, price asc."""
31
+ providers = _get_providers(registry_contract)
32
+
33
+ candidates = [
34
+ p for p in providers
35
+ if p.model == model
36
+ and (max_price is None or p.price_per_token <= max_price)
37
+ and p.reputation >= min_reputation
38
+ ]
39
+
40
+ if not candidates:
41
+ raise RuntimeError(
42
+ f"No providers available for model '{model}' "
43
+ f"within price/reputation constraints."
44
+ )
45
+
46
+ candidates.sort(key=lambda p: (-p.reputation, p.price_per_token))
47
+ return candidates[0]
48
+
49
+
50
+ def fetch_pubkey(provider: Provider) -> str:
51
+ """Fetch current TEE pubkey from provider (may rotate on restart)."""
52
+ resp = requests.get(f"{provider.endpoint}/pubkey", timeout=10)
53
+ resp.raise_for_status()
54
+ return resp.json()["pubkey"]
55
+
56
+
57
+ def _get_providers(registry_contract: str | None) -> list[Provider]:
58
+ if registry_contract is None:
59
+ return _get_providers_stub()
60
+ return _get_providers_from_chain(registry_contract)
61
+
62
+
63
+ def _get_providers_from_chain(registry_contract: str) -> list[Provider]:
64
+ """
65
+ Query Midnight indexer GraphQL for active providers in the ProviderRegistry.
66
+ The contract's export ledger maps are publicly readable.
67
+ """
68
+ query = """
69
+ query GetContractState($address: String!) {
70
+ contract(address: $address) {
71
+ state {
72
+ ... on ContractState {
73
+ ledger {
74
+ ... on ZkaiProviderRegistryLedger {
75
+ provider_active { entries { key value } }
76
+ provider_endpoint { entries { key value } }
77
+ provider_model { entries { key value } }
78
+ provider_price { entries { key value } }
79
+ provider_reputation { entries { key value } }
80
+ provider_pubkey { entries { key value } }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
87
+ """
88
+ try:
89
+ resp = requests.post(
90
+ INDEXER_URL,
91
+ json={"query": query, "variables": {"address": registry_contract}},
92
+ timeout=15,
93
+ )
94
+ resp.raise_for_status()
95
+ data = resp.json()
96
+ ledger = data.get("data", {}).get("contract", {}).get("state", {}).get("ledger", {})
97
+
98
+ if not ledger:
99
+ # Indexer schema may differ — fall back to stub with a warning
100
+ print("Warning: Could not parse on-chain providers, falling back to stub")
101
+ return _get_providers_stub()
102
+
103
+ active_entries = {e["key"]: e["value"] for e in ledger.get("provider_active", {}).get("entries", [])}
104
+ endpoint_entries = {e["key"]: e["value"] for e in ledger.get("provider_endpoint", {}).get("entries", [])}
105
+ model_entries = {e["key"]: e["value"] for e in ledger.get("provider_model", {}).get("entries", [])}
106
+ price_entries = {e["key"]: e["value"] for e in ledger.get("provider_price", {}).get("entries", [])}
107
+ rep_entries = {e["key"]: e["value"] for e in ledger.get("provider_reputation", {}).get("entries", [])}
108
+ pubkey_entries = {e["key"]: e["value"] for e in ledger.get("provider_pubkey", {}).get("entries", [])}
109
+
110
+ providers = []
111
+ for pid, active in active_entries.items():
112
+ if not active:
113
+ continue
114
+ providers.append(Provider(
115
+ id=pid,
116
+ endpoint=endpoint_entries.get(pid, ""),
117
+ pubkey=pubkey_entries.get(pid, ""),
118
+ model=model_entries.get(pid, ""),
119
+ price_per_token=float(price_entries.get(pid, 0)),
120
+ reputation=float(rep_entries.get(pid, 500000)) / 1_000_000,
121
+ stake=0.0,
122
+ ))
123
+ return providers if providers else _get_providers_stub()
124
+
125
+ except Exception as e:
126
+ print(f"Warning: Failed to fetch on-chain providers ({e}), falling back to stub")
127
+ return _get_providers_stub()
128
+
129
+
130
+ def _get_providers_stub() -> list[Provider]:
131
+ """Local dev stub — points to localhost enclave."""
132
+ return [
133
+ Provider(
134
+ id="local-dev",
135
+ endpoint="http://localhost:8080",
136
+ pubkey="",
137
+ model="qwen2.5-1.5b",
138
+ price_per_token=0.0,
139
+ reputation=1.0,
140
+ stake=0.0,
141
+ )
142
+ ]
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.4
2
+ Name: zkai
3
+ Version: 0.5.0
4
+ Summary: Private, verifiable AI inference on 0G chain — drop-in OpenAI-compatible SDK
5
+ Author-email: ZKai <team@zkai.network>
6
+ License: MIT
7
+ Project-URL: Homepage, https://zkai-ether-og.vercel.app
8
+ Project-URL: Repository, https://github.com/skyyycodes/zkai-eth
9
+ Project-URL: Issues, https://github.com/skyyycodes/zkai-eth/issues
10
+ Keywords: ai,inference,openai,llm,tee,tdx,attestation,blockchain,0g,verifiable,privacy,encryption
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Security :: Cryptography
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: cryptography>=43.0
24
+ Requires-Dist: requests>=2.32
25
+ Requires-Dist: pydantic>=2.9
26
+ Provides-Extra: langchain
27
+ Requires-Dist: langchain-core>=0.3; extra == "langchain"
28
+
29
+ # zkai
30
+
31
+ OpenAI-compatible Python SDK for [ZKai](https://zkai-ether-og.vercel.app) — private, verifiable AI inference on 0G chain.
32
+
33
+ ZKai sends your prompt through a Trusted Execution Environment (Intel TDX), encrypts it client-side so even the gateway cannot read it, and anchors a SHA-256 attestation of every inference on the 0G chain. You get back a normal OpenAI-style response plus an on-chain receipt.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install zkai
39
+ ```
40
+
41
+ Optional LangChain adapter:
42
+
43
+ ```bash
44
+ pip install "zkai[langchain]"
45
+ ```
46
+
47
+ ## Quick start
48
+
49
+ ```python
50
+ from zkai import ZKai
51
+
52
+ client = ZKai(api_key="zkai-...") # get one at https://zkai-ether-og.vercel.app
53
+
54
+ response = client.chat.completions.create(
55
+ model="qwen2.5:1.5b",
56
+ messages=[{"role": "user", "content": "Hello!"}],
57
+ )
58
+
59
+ print(response.choices[0].message.content)
60
+ print("Attestation hash:", response.attestation_hash)
61
+ ```
62
+
63
+ The response is decrypted locally — the gateway sees only ciphertext.
64
+ The attestation hash is anchored on 0G mainnet and can be verified independently.
65
+
66
+ ## How it works (gateway mode, default)
67
+
68
+ 1. The SDK fetches the enclave's X25519 public key from the gateway.
69
+ 2. It encrypts your prompt locally with ECDH + ChaCha20-Poly1305.
70
+ 3. It sends only the ciphertext to the gateway.
71
+ 4. The gateway routes the opaque blob to a TDX-sealed enclave running the requested model.
72
+ 5. The enclave decrypts inside sealed memory, runs the model, encrypts the response, and emits a SHA-256 attestation hash.
73
+ 6. The attestation lands on the on-chain `AttestationRegistry` contract.
74
+ 7. The SDK decrypts the response locally and returns it to you.
75
+
76
+ End-to-end, the gateway never sees plaintext.
77
+
78
+ ## Configuration
79
+
80
+ ```python
81
+ ZKai(
82
+ api_key="zkai-...", # issued from the dashboard
83
+ base_url="https://zkai-ether-og.vercel.app", # default
84
+ encrypted=True, # default; set False to use the legacy plaintext path
85
+ provider_endpoint=None, # set to bypass gateway and hit a provider directly
86
+ skip_attestation=False, # only for development; do not disable in prod
87
+ )
88
+ ```
89
+
90
+ | Argument | Type | Default | Description |
91
+ |---|---|---|---|
92
+ | `api_key` | `str | None` | `None` | Sent as `X-API-Key`. Required for the hosted gateway. |
93
+ | `base_url` | `str | None` | `https://zkai-ether-og.vercel.app` | Override to point at a self-hosted gateway. |
94
+ | `encrypted` | `bool` | `True` | When `True`, prompts are encrypted client-side. Set `False` for legacy clients. |
95
+ | `provider_endpoint` | `str | None` | `None` | When set, bypasses the gateway and talks directly to a provider's `/infer` endpoint. |
96
+ | `skip_attestation` | `bool` | `False` | Disables attestation verification. Useful only for dev loops. |
97
+
98
+ ## OpenAI compatibility
99
+
100
+ The response object mirrors OpenAI's chat completion shape:
101
+
102
+ ```python
103
+ response.id
104
+ response.model
105
+ response.choices[0].message.content
106
+ response.choices[0].finish_reason
107
+ response.usage.prompt_tokens
108
+ response.usage.completion_tokens
109
+
110
+ # ZKai-specific:
111
+ response.attestation_hash # on-chain commitment
112
+ ```
113
+
114
+ Drop-in replacement for most OpenAI SDK call sites — just swap the client class.
115
+
116
+ ## On-chain components (0G mainnet, chain ID 16661)
117
+
118
+ | Contract | Address |
119
+ |---|---|
120
+ | ProviderRegistry | `0x6D400F5D1DcCaA3e98E3dE17322aA23DE38bAC99` |
121
+ | PaymentEscrow | `0xb2C7c0F7a4C2877319E8Ed1Fae0bf3C705b6Fc4C` |
122
+ | AttestationRegistry | `0x8c8Ae0A113084268D181fd1cf23d611DC2EAa2B2` |
123
+
124
+ Verify any attestation hash on the [0G explorer](https://chainscan.0g.ai).
125
+
126
+ ## Running your own provider
127
+
128
+ See the companion [`zkai-cli`](https://pypi.org/project/zkai-cli/) package:
129
+
130
+ ```bash
131
+ pip install zkai-cli
132
+ zkai init && zkai start && zkai register
133
+ ```
134
+
135
+ ## Links
136
+
137
+ - Dashboard — https://zkai-ether-og.vercel.app
138
+ - Repository — https://github.com/skyyycodes/zkai-eth
139
+ - Provider CLI — https://pypi.org/project/zkai-cli/
140
+
141
+ ## License
142
+
143
+ MIT
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ zkai/__init__.py
4
+ zkai/attestation.py
5
+ zkai/bridge.py
6
+ zkai/client.py
7
+ zkai/crypto.py
8
+ zkai/langchain.py
9
+ zkai/payment.py
10
+ zkai/provider.py
11
+ zkai.egg-info/PKG-INFO
12
+ zkai.egg-info/SOURCES.txt
13
+ zkai.egg-info/dependency_links.txt
14
+ zkai.egg-info/requires.txt
15
+ zkai.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ cryptography>=43.0
2
+ requests>=2.32
3
+ pydantic>=2.9
4
+
5
+ [langchain]
6
+ langchain-core>=0.3
@@ -0,0 +1 @@
1
+ zkai