attestic 0.1.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.
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: attestic
3
+ Version: 0.1.0
4
+ Summary: Thin client for the Attestic AI gateway: pre-configured OpenAI/Anthropic clients + offline signed-receipt verification.
5
+ Author: VelarIQ Inc.
6
+ License: MIT
7
+ Project-URL: Homepage, https://attestic.ai
8
+ Project-URL: Documentation, https://attestic.ai/developers
9
+ Keywords: attestic,ai,gateway,provenance,openai,anthropic,llm
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: cryptography>=41
13
+ Provides-Extra: openai
14
+ Requires-Dist: openai>=1.30; extra == "openai"
15
+ Provides-Extra: anthropic
16
+ Requires-Dist: anthropic>=0.40; extra == "anthropic"
17
+ Provides-Extra: all
18
+ Requires-Dist: openai>=1.30; extra == "all"
19
+ Requires-Dist: anthropic>=0.40; extra == "all"
20
+
21
+ # attestic (Python)
22
+
23
+ Thin client for the [Attestic](https://attestic.ai) AI gateway.
24
+
25
+ Attestic is drop-in **OpenAI- and Anthropic-compatible**, so you can also just point
26
+ the OpenAI or Anthropic SDK at `https://api.attestic.ai`. This package adds two
27
+ conveniences: a pre-configured client, and **offline verification of the signed
28
+ provenance receipt** every call produces.
29
+
30
+ ```bash
31
+ pip install attestic[all] # or attestic[openai] / attestic[anthropic]
32
+ ```
33
+
34
+ ```python
35
+ from attestic import Attestic
36
+
37
+ at = Attestic(api_key="atk_...") # mint a key at attestic.ai/api-keys
38
+
39
+ # OpenAI-style
40
+ r = at.openai.chat.completions.create(
41
+ model="gpt-5.5",
42
+ messages=[{"role": "user", "content": "Explain provenance in one sentence."}],
43
+ )
44
+ print(r.choices[0].message.content)
45
+
46
+ # Anthropic / Claude-style (same key, same gateway)
47
+ m = at.anthropic.messages.create(
48
+ model="claude-fable-5", max_tokens=256,
49
+ messages=[{"role": "user", "content": "hi"}],
50
+ )
51
+
52
+ # Verify a call's receipt OFFLINE — no trust in Attestic required
53
+ receipt_id = "..." # from the x-attestic-receipt-id response header
54
+ assert at.verify(receipt_id) # checks Ed25519 signature + Merkle inclusion
55
+ ```
56
+
57
+ Models route by id across OpenAI, Anthropic, Google, and NVIDIA open-weight —
58
+ `gpt-5.5`, `claude-fable-5`, `gemini-3.1-pro-preview`, `meta/llama-3.3-70b-instruct`, …
59
+
60
+ ## Without this SDK
61
+ Nothing special required:
62
+
63
+ ```python
64
+ from openai import OpenAI
65
+ client = OpenAI(base_url="https://api.attestic.ai/v1", api_key="atk_...")
66
+ ```
67
+ ```python
68
+ from anthropic import Anthropic
69
+ client = Anthropic(base_url="https://api.attestic.ai", api_key="atk_...")
70
+ ```
@@ -0,0 +1,50 @@
1
+ # attestic (Python)
2
+
3
+ Thin client for the [Attestic](https://attestic.ai) AI gateway.
4
+
5
+ Attestic is drop-in **OpenAI- and Anthropic-compatible**, so you can also just point
6
+ the OpenAI or Anthropic SDK at `https://api.attestic.ai`. This package adds two
7
+ conveniences: a pre-configured client, and **offline verification of the signed
8
+ provenance receipt** every call produces.
9
+
10
+ ```bash
11
+ pip install attestic[all] # or attestic[openai] / attestic[anthropic]
12
+ ```
13
+
14
+ ```python
15
+ from attestic import Attestic
16
+
17
+ at = Attestic(api_key="atk_...") # mint a key at attestic.ai/api-keys
18
+
19
+ # OpenAI-style
20
+ r = at.openai.chat.completions.create(
21
+ model="gpt-5.5",
22
+ messages=[{"role": "user", "content": "Explain provenance in one sentence."}],
23
+ )
24
+ print(r.choices[0].message.content)
25
+
26
+ # Anthropic / Claude-style (same key, same gateway)
27
+ m = at.anthropic.messages.create(
28
+ model="claude-fable-5", max_tokens=256,
29
+ messages=[{"role": "user", "content": "hi"}],
30
+ )
31
+
32
+ # Verify a call's receipt OFFLINE — no trust in Attestic required
33
+ receipt_id = "..." # from the x-attestic-receipt-id response header
34
+ assert at.verify(receipt_id) # checks Ed25519 signature + Merkle inclusion
35
+ ```
36
+
37
+ Models route by id across OpenAI, Anthropic, Google, and NVIDIA open-weight —
38
+ `gpt-5.5`, `claude-fable-5`, `gemini-3.1-pro-preview`, `meta/llama-3.3-70b-instruct`, …
39
+
40
+ ## Without this SDK
41
+ Nothing special required:
42
+
43
+ ```python
44
+ from openai import OpenAI
45
+ client = OpenAI(base_url="https://api.attestic.ai/v1", api_key="atk_...")
46
+ ```
47
+ ```python
48
+ from anthropic import Anthropic
49
+ client = Anthropic(base_url="https://api.attestic.ai", api_key="atk_...")
50
+ ```
@@ -0,0 +1,127 @@
1
+ """Attestic — thin client for the Attestic AI gateway.
2
+
3
+ Attestic is drop-in OpenAI- and Anthropic-compatible, so you don't *need* a custom
4
+ SDK — point the OpenAI or Anthropic SDK at the gateway and go. This package is a
5
+ convenience layer that (a) hands you a pre-configured client and (b) does the thing
6
+ that makes Attestic *Attestic*: verify a call's signed provenance receipt **offline**.
7
+
8
+ from attestic import Attestic
9
+
10
+ at = Attestic(api_key="atk_...") # base_url defaults to api.attestic.ai
11
+ resp = at.openai.chat.completions.create( # the real OpenAI client, pre-pointed
12
+ model="gpt-5.5", messages=[{"role": "user", "content": "hi"}],
13
+ )
14
+ rid = resp.id # or read the x-attestic-receipt-id response header
15
+ assert at.verify(rid) # Ed25519 + Merkle, no network trust
16
+
17
+ msg = at.anthropic.messages.create( # the Claude client, also pre-pointed
18
+ model="claude-fable-5", max_tokens=256,
19
+ messages=[{"role": "user", "content": "hi"}],
20
+ )
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import hashlib
25
+ import json
26
+ import urllib.request
27
+ from typing import Any, Optional
28
+
29
+ __version__ = "0.1.0"
30
+
31
+ DEFAULT_BASE = "https://api.attestic.ai"
32
+
33
+
34
+ class Attestic:
35
+ """A pre-configured handle to the Attestic gateway + offline receipt verification.
36
+
37
+ By default (``use_oauth=True``) the long-lived ``atk_`` key is exchanged for a
38
+ short-lived (1h) access token that does the actual calls, refreshed automatically.
39
+ The key itself never leaves this object after construction — so a leaked request
40
+ credential expires within the hour. Set ``use_oauth=False`` to send the key directly."""
41
+
42
+ def __init__(self, api_key: str, base_url: str = DEFAULT_BASE, use_oauth: bool = True):
43
+ self.api_key = api_key
44
+ self.base_url = base_url.rstrip("/")
45
+ self._v1 = f"{self.base_url}/v1"
46
+ self.use_oauth = use_oauth
47
+ self._token: Optional[str] = None
48
+ self._token_exp: float = 0.0
49
+ self._openai = None
50
+ self._openai_cred: Optional[str] = None
51
+ self._anthropic = None
52
+ self._anthropic_cred: Optional[str] = None
53
+
54
+ def _credential(self) -> str:
55
+ """The bearer to call the API with: a fresh short-lived token (OAuth) or the key."""
56
+ if not self.use_oauth:
57
+ return self.api_key
58
+ import time as _time
59
+ if self._token and _time.time() < self._token_exp - 60:
60
+ return self._token
61
+ req = urllib.request.Request(
62
+ f"{self._v1}/oauth/token",
63
+ data=json.dumps({"api_key": self.api_key}).encode(),
64
+ headers={"Content-Type": "application/json"},
65
+ )
66
+ d = json.load(urllib.request.urlopen(req, timeout=30))
67
+ self._token = d["access_token"]
68
+ self._token_exp = _time.time() + float(d.get("expires_in", 3600))
69
+ return self._token
70
+
71
+ def access_token(self) -> str:
72
+ """The current short-lived access token (minted/refreshed on demand)."""
73
+ return self._credential()
74
+
75
+ @property
76
+ def openai(self):
77
+ """A `openai.OpenAI` client pointed at Attestic (lazy; auto-refreshes the token)."""
78
+ cred = self._credential()
79
+ if self._openai is None or self._openai_cred != cred:
80
+ from openai import OpenAI
81
+ self._openai = OpenAI(base_url=self._v1, api_key=cred)
82
+ self._openai_cred = cred
83
+ return self._openai
84
+
85
+ @property
86
+ def anthropic(self):
87
+ """An `anthropic.Anthropic` client pointed at Attestic (lazy; auto-refreshes the token)."""
88
+ cred = self._credential()
89
+ if self._anthropic is None or self._anthropic_cred != cred:
90
+ from anthropic import Anthropic
91
+ self._anthropic = Anthropic(base_url=self.base_url, api_key=cred)
92
+ self._anthropic_cred = cred
93
+ return self._anthropic
94
+
95
+ # --- receipts -----------------------------------------------------------
96
+ def fetch_receipt(self, receipt_id: str) -> dict:
97
+ """Fetch the verifiable receipt bundle for a call (own-tenant only)."""
98
+ req = urllib.request.Request(
99
+ f"{self._v1}/receipts/{receipt_id}",
100
+ headers={"Authorization": f"Bearer {self._credential()}"},
101
+ )
102
+ return json.load(urllib.request.urlopen(req, timeout=30))
103
+
104
+ def verify(self, receipt_id: str) -> bool:
105
+ """Fetch a receipt and verify it OFFLINE: the Ed25519 signature over the
106
+ canonical record AND the Merkle inclusion proof. Returns True iff both hold."""
107
+ return verify_receipt(self.fetch_receipt(receipt_id))
108
+
109
+
110
+ def verify_receipt(bundle: dict) -> bool:
111
+ """Verify an Attestic receipt bundle with no trust in the gateway:
112
+ 1. Ed25519: `public_key` verifies `signature` over `signed_bytes`.
113
+ 2. Merkle: folding `leaf_hash` up `proof` lands on `merkle.root`.
114
+ Requires the `cryptography` package for the signature check.
115
+ Fails closed: any malformed/missing field returns False rather than raising."""
116
+ try:
117
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
118
+ pk = Ed25519PublicKey.from_public_bytes(bytes.fromhex(bundle["public_key"]))
119
+ pk.verify(bytes.fromhex(bundle["signature"]), bytes.fromhex(bundle["signed_bytes"]))
120
+ m = bundle.get("merkle") or {}
121
+ h = bytes.fromhex(m["leaf_hash"])
122
+ for step in m.get("proof", []):
123
+ sib = bytes.fromhex(step["sibling"])
124
+ h = hashlib.sha256((sib + h) if step.get("side") == "left" else (h + sib)).digest()
125
+ return h.hex() == m.get("root")
126
+ except Exception:
127
+ return False
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: attestic
3
+ Version: 0.1.0
4
+ Summary: Thin client for the Attestic AI gateway: pre-configured OpenAI/Anthropic clients + offline signed-receipt verification.
5
+ Author: VelarIQ Inc.
6
+ License: MIT
7
+ Project-URL: Homepage, https://attestic.ai
8
+ Project-URL: Documentation, https://attestic.ai/developers
9
+ Keywords: attestic,ai,gateway,provenance,openai,anthropic,llm
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: cryptography>=41
13
+ Provides-Extra: openai
14
+ Requires-Dist: openai>=1.30; extra == "openai"
15
+ Provides-Extra: anthropic
16
+ Requires-Dist: anthropic>=0.40; extra == "anthropic"
17
+ Provides-Extra: all
18
+ Requires-Dist: openai>=1.30; extra == "all"
19
+ Requires-Dist: anthropic>=0.40; extra == "all"
20
+
21
+ # attestic (Python)
22
+
23
+ Thin client for the [Attestic](https://attestic.ai) AI gateway.
24
+
25
+ Attestic is drop-in **OpenAI- and Anthropic-compatible**, so you can also just point
26
+ the OpenAI or Anthropic SDK at `https://api.attestic.ai`. This package adds two
27
+ conveniences: a pre-configured client, and **offline verification of the signed
28
+ provenance receipt** every call produces.
29
+
30
+ ```bash
31
+ pip install attestic[all] # or attestic[openai] / attestic[anthropic]
32
+ ```
33
+
34
+ ```python
35
+ from attestic import Attestic
36
+
37
+ at = Attestic(api_key="atk_...") # mint a key at attestic.ai/api-keys
38
+
39
+ # OpenAI-style
40
+ r = at.openai.chat.completions.create(
41
+ model="gpt-5.5",
42
+ messages=[{"role": "user", "content": "Explain provenance in one sentence."}],
43
+ )
44
+ print(r.choices[0].message.content)
45
+
46
+ # Anthropic / Claude-style (same key, same gateway)
47
+ m = at.anthropic.messages.create(
48
+ model="claude-fable-5", max_tokens=256,
49
+ messages=[{"role": "user", "content": "hi"}],
50
+ )
51
+
52
+ # Verify a call's receipt OFFLINE — no trust in Attestic required
53
+ receipt_id = "..." # from the x-attestic-receipt-id response header
54
+ assert at.verify(receipt_id) # checks Ed25519 signature + Merkle inclusion
55
+ ```
56
+
57
+ Models route by id across OpenAI, Anthropic, Google, and NVIDIA open-weight —
58
+ `gpt-5.5`, `claude-fable-5`, `gemini-3.1-pro-preview`, `meta/llama-3.3-70b-instruct`, …
59
+
60
+ ## Without this SDK
61
+ Nothing special required:
62
+
63
+ ```python
64
+ from openai import OpenAI
65
+ client = OpenAI(base_url="https://api.attestic.ai/v1", api_key="atk_...")
66
+ ```
67
+ ```python
68
+ from anthropic import Anthropic
69
+ client = Anthropic(base_url="https://api.attestic.ai", api_key="atk_...")
70
+ ```
@@ -0,0 +1,8 @@
1
+ README.md
2
+ pyproject.toml
3
+ attestic/__init__.py
4
+ attestic.egg-info/PKG-INFO
5
+ attestic.egg-info/SOURCES.txt
6
+ attestic.egg-info/dependency_links.txt
7
+ attestic.egg-info/requires.txt
8
+ attestic.egg-info/top_level.txt
@@ -0,0 +1,11 @@
1
+ cryptography>=41
2
+
3
+ [all]
4
+ openai>=1.30
5
+ anthropic>=0.40
6
+
7
+ [anthropic]
8
+ anthropic>=0.40
9
+
10
+ [openai]
11
+ openai>=1.30
@@ -0,0 +1 @@
1
+ attestic
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "attestic"
7
+ version = "0.1.0"
8
+ description = "Thin client for the Attestic AI gateway: pre-configured OpenAI/Anthropic clients + offline signed-receipt verification."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "VelarIQ Inc." }]
13
+ keywords = ["attestic", "ai", "gateway", "provenance", "openai", "anthropic", "llm"]
14
+ dependencies = ["cryptography>=41"]
15
+
16
+ [project.optional-dependencies]
17
+ openai = ["openai>=1.30"]
18
+ anthropic = ["anthropic>=0.40"]
19
+ all = ["openai>=1.30", "anthropic>=0.40"]
20
+
21
+ [project.urls]
22
+ Homepage = "https://attestic.ai"
23
+ Documentation = "https://attestic.ai/developers"
24
+
25
+ [tool.setuptools.packages.find]
26
+ include = ["attestic*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+