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.
- attestic-0.1.0/PKG-INFO +70 -0
- attestic-0.1.0/README.md +50 -0
- attestic-0.1.0/attestic/__init__.py +127 -0
- attestic-0.1.0/attestic.egg-info/PKG-INFO +70 -0
- attestic-0.1.0/attestic.egg-info/SOURCES.txt +8 -0
- attestic-0.1.0/attestic.egg-info/dependency_links.txt +1 -0
- attestic-0.1.0/attestic.egg-info/requires.txt +11 -0
- attestic-0.1.0/attestic.egg-info/top_level.txt +1 -0
- attestic-0.1.0/pyproject.toml +26 -0
- attestic-0.1.0/setup.cfg +4 -0
attestic-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+
```
|
attestic-0.1.0/README.md
ADDED
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -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*"]
|
attestic-0.1.0/setup.cfg
ADDED