aotrust-protocol 2.3.6__tar.gz → 2.4.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,199 @@
1
+ Metadata-Version: 2.4
2
+ Name: aotrust-protocol
3
+ Version: 2.4.0
4
+ Summary: Asyncio-native SDK for A&O Trust Layer — Agent Notary Service
5
+ Author: A&O Trust Layer
6
+ License: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Topic :: Security :: Cryptography
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: aiohttp>=3.9
19
+ Requires-Dist: pydantic>=2.0
20
+ Requires-Dist: pynacl>=1.5
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.0; extra == "dev"
23
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
24
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
25
+ Requires-Dist: aioresponses>=0.7; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # aotrust-protocol SDK
29
+
30
+ <!-- mcp-name: link.aotrust/notary -->
31
+
32
+ Asyncio-native Python SDK for the AOTrust Notary API — cryptographic proof-of-existence for AI agent outputs.
33
+
34
+ > **Why protocol-level proofs?** An ERC-8004 empirical study (2026) measured that **>98% of agent feedback records carry no proof of an underlying interaction** (no payment proof, no task linkage); 59–91% of reviewers on three chains participated in coordinated Sybil campaigns, and manipulating a reputation score past a trust threshold costs as little as **$0.0027**. The agentic economy is moving from *statements* to *verifiable claims*: a PDR (239 bytes, Ed25519-signed, hash-chained, NEAR-anchored) turns any output or payment into independent evidence — verifiable by anyone, without trusting the provider's database.
35
+ > *(Refs: arXiv:2610.12948 [cs.CR], 2026; ERC-8004 Sybil measurements, Xiong et al. 2026.)*
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install aotrust-protocol
41
+ ```
42
+
43
+ ## Quickstart
44
+
45
+ ### Free tier — no API key, no wallet (fastest way to try)
46
+
47
+ ```python
48
+ import asyncio, hashlib
49
+ from agent_notary import NotaryClient
50
+
51
+ async def main():
52
+ # IMPORTANT: no /v1 suffix — the SDK appends API paths itself.
53
+ client = NotaryClient(base_url="https://api.aotrust.link")
54
+
55
+ work_hash = hashlib.sha256(b"my artifact text").hexdigest()
56
+ result = await client.shield_free(work_hash)
57
+ job_id = result["job_id"] # keep this UUID — it is the handle
58
+ print("job_id:", job_id)
59
+
60
+ # Check status BY job_id (not by the artifact text!)
61
+ status = await client.get_status(job_id)
62
+ print("status:", status.status) # PENDING → anchored (anchor batches)
63
+
64
+ # Fetch + verify the PDR
65
+ pdr = await client.get_pdr(job_id)
66
+ print("verify in browser:", pdr.verify_url)
67
+ assert (await client.verify_pdr(pdr.pdr_b64))["valid"]
68
+
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ Free tier: 5 PDR / 24h per IP. Examples: [`examples/04_free_tier.py`](examples/04_free_tier.py).
73
+
74
+ ### Witness mode (pre-paid tx_hash)
75
+
76
+ ```python
77
+ import asyncio
78
+ from agent_notary import NotaryClient, NotarizeRequest
79
+
80
+ async def main():
81
+ client = NotaryClient(
82
+ api_key="your-api-key",
83
+ base_url="https://api.aotrust.link" # no /v1 suffix!
84
+ )
85
+
86
+ # Submit notarization
87
+ req = NotarizeRequest(
88
+ tx_hash="EzrfDW5b...",
89
+ work_hash="599d6999...",
90
+ agent_sig="base64_sig_A...",
91
+ agent_pubkey="aff91a18...",
92
+ )
93
+ result = await client.notarize(req)
94
+ print(f"Job: {result.job_id}")
95
+
96
+ # Poll for PDR — status/pdr calls always take the job_id returned above
97
+ status = await client.wait_for_pdr(result.job_id, timeout=60)
98
+ if status.is_anchored:
99
+ pdr = await client.get_pdr(result.job_id)
100
+ assert pdr.verify(client.notary_pubkey)
101
+ print("PDR Valid: YES")
102
+
103
+ asyncio.run(main())
104
+ ```
105
+
106
+ > **Why no `/v1`?** `base_url` must be `https://api.aotrust.link`. The SDK
107
+ > already builds `/v1/...` request paths; adding `/v1` yourself produces
108
+ > `/v1/v1/...` → HTTP 404 on every call. This bit a real user (2026-09-29:
109
+ > quickstart copy-paste + status lookups by artifact text instead of job_id).
110
+
111
+ ## Configuration
112
+
113
+ | Env Var | Constructor Arg | Description |
114
+ |---------|-----------------|-------------|
115
+ | `NOTARY_API_KEY` | `api_key` | API key for auth |
116
+ | `NOTARY_API_URL` | `base_url` | API base URL |
117
+ | `NOTARY_PUBKEY` | `notary_pubkey` | Notary Ed25519 public key (hex) |
118
+
119
+ ## Endpoints
120
+
121
+ | Method | Path | Description |
122
+ |--------|------|-------------|
123
+ | POST | `/v1/notarize` | Submit notarization |
124
+ | GET | `/v1/status/{job_id}` | Poll job status |
125
+ | GET | `/v1/pdr/{job_id}` | Get PDR |
126
+ | POST | `/v1/notarize/quote` | Get price quote |
127
+ | GET | `/.well-known/agent.json` | MCP Server Card |
128
+ | GET | `/openapi.json` | OpenAPI 3.0 spec |
129
+
130
+ ## PDR Verification
131
+
132
+ ```python
133
+ pdr = await client.get_pdr(job_id)
134
+ is_valid = pdr.verify(notary_pubkey_hex="9c7d64bb...")
135
+ ```
136
+
137
+ Uses NEP-413 raw-buffer verification. No SHA256 pre-hash.
138
+
139
+ ## Error Handling
140
+
141
+ ```python
142
+ from agent_notary.exceptions import (
143
+ NotaryAuthError, # 401 (authenticated CI surfaces; anonymous x402 gives 402 instead)
144
+ NotaryPaymentError, # 402 x402-challenge / 403
145
+ NotaryNotFoundError, # 404
146
+ NotaryValidationError, # 400/422 — invalid params, INVALID_AGENT_SIGNATURE
147
+ NotaryConflictError, # 409 — tx_hash/job_id already used (idempotency)
148
+ NotaryServerError, # 5xx, carries status_code
149
+ NotaryError, # base — safety net
150
+ )
151
+
152
+ try:
153
+ result = await client.notarize(req)
154
+ except NotaryAuthError:
155
+ print("Invalid API key")
156
+ except NotaryPaymentError as e:
157
+ print(f"Payment/sig failed: {e}")
158
+ except NotaryValidationError as e:
159
+ print(f"Bad request or invalid signature: {e}")
160
+ except NotaryConflictError:
161
+ print("This tx_hash/job_id was already used — generate a new one")
162
+ except NotaryError as e:
163
+ print(f"Other SDK error: {e}")
164
+ ```
165
+
166
+ ## Development
167
+
168
+ ```bash
169
+ pip install -e ".[dev]"
170
+ pytest tests/ -v
171
+ ```
172
+
173
+ ## Payment binding (NEAR rail)
174
+
175
+ NEAR payments must be provably linked to the account that sent them. Pass
176
+ `payer_account` (the NEAR account that paid) together with `bind_sig` — a
177
+ base64 Ed25519 **NEP-413 raw-buffer** signature (no SHA-256 pre-hash) over:
178
+
179
+ ```
180
+ AOTrust AITP-BIND v1
181
+ work_hash:<64 hex>
182
+ tx_hash:<tx hash exactly as submitted>
183
+ ```
184
+
185
+ The server resolves the account's public key via RPC and checks
186
+ `transaction.signer_id == payer_account`; a mismatch is rejected with
187
+ `403 PAYMENT_BIND_SIGNER_MISMATCH`, a bad signature with
188
+ `403 PAYMENT_BIND_INVALID`.
189
+
190
+ If `NEAR_PRIVATE_KEY` is set in the environment (or `near_private_key=` is
191
+ passed), the SDK signs the canonical message itself and fills `bind_sig`:
192
+
193
+ ```python
194
+ req = NotarizeRequest(tx_hash=tx, work_hash=wh)
195
+ await client.notarize(req, payer_account="alice.near")
196
+ ```
197
+
198
+ Server mode `AITP_BIND_MODE=warn` (default) accepts unbound requests with a
199
+ `PAYMENT_UNBOUND` log line; `enforce` requires the fields.
@@ -0,0 +1,172 @@
1
+ # aotrust-protocol SDK
2
+
3
+ <!-- mcp-name: link.aotrust/notary -->
4
+
5
+ Asyncio-native Python SDK for the AOTrust Notary API — cryptographic proof-of-existence for AI agent outputs.
6
+
7
+ > **Why protocol-level proofs?** An ERC-8004 empirical study (2026) measured that **>98% of agent feedback records carry no proof of an underlying interaction** (no payment proof, no task linkage); 59–91% of reviewers on three chains participated in coordinated Sybil campaigns, and manipulating a reputation score past a trust threshold costs as little as **$0.0027**. The agentic economy is moving from *statements* to *verifiable claims*: a PDR (239 bytes, Ed25519-signed, hash-chained, NEAR-anchored) turns any output or payment into independent evidence — verifiable by anyone, without trusting the provider's database.
8
+ > *(Refs: arXiv:2610.12948 [cs.CR], 2026; ERC-8004 Sybil measurements, Xiong et al. 2026.)*
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install aotrust-protocol
14
+ ```
15
+
16
+ ## Quickstart
17
+
18
+ ### Free tier — no API key, no wallet (fastest way to try)
19
+
20
+ ```python
21
+ import asyncio, hashlib
22
+ from agent_notary import NotaryClient
23
+
24
+ async def main():
25
+ # IMPORTANT: no /v1 suffix — the SDK appends API paths itself.
26
+ client = NotaryClient(base_url="https://api.aotrust.link")
27
+
28
+ work_hash = hashlib.sha256(b"my artifact text").hexdigest()
29
+ result = await client.shield_free(work_hash)
30
+ job_id = result["job_id"] # keep this UUID — it is the handle
31
+ print("job_id:", job_id)
32
+
33
+ # Check status BY job_id (not by the artifact text!)
34
+ status = await client.get_status(job_id)
35
+ print("status:", status.status) # PENDING → anchored (anchor batches)
36
+
37
+ # Fetch + verify the PDR
38
+ pdr = await client.get_pdr(job_id)
39
+ print("verify in browser:", pdr.verify_url)
40
+ assert (await client.verify_pdr(pdr.pdr_b64))["valid"]
41
+
42
+ asyncio.run(main())
43
+ ```
44
+
45
+ Free tier: 5 PDR / 24h per IP. Examples: [`examples/04_free_tier.py`](examples/04_free_tier.py).
46
+
47
+ ### Witness mode (pre-paid tx_hash)
48
+
49
+ ```python
50
+ import asyncio
51
+ from agent_notary import NotaryClient, NotarizeRequest
52
+
53
+ async def main():
54
+ client = NotaryClient(
55
+ api_key="your-api-key",
56
+ base_url="https://api.aotrust.link" # no /v1 suffix!
57
+ )
58
+
59
+ # Submit notarization
60
+ req = NotarizeRequest(
61
+ tx_hash="EzrfDW5b...",
62
+ work_hash="599d6999...",
63
+ agent_sig="base64_sig_A...",
64
+ agent_pubkey="aff91a18...",
65
+ )
66
+ result = await client.notarize(req)
67
+ print(f"Job: {result.job_id}")
68
+
69
+ # Poll for PDR — status/pdr calls always take the job_id returned above
70
+ status = await client.wait_for_pdr(result.job_id, timeout=60)
71
+ if status.is_anchored:
72
+ pdr = await client.get_pdr(result.job_id)
73
+ assert pdr.verify(client.notary_pubkey)
74
+ print("PDR Valid: YES")
75
+
76
+ asyncio.run(main())
77
+ ```
78
+
79
+ > **Why no `/v1`?** `base_url` must be `https://api.aotrust.link`. The SDK
80
+ > already builds `/v1/...` request paths; adding `/v1` yourself produces
81
+ > `/v1/v1/...` → HTTP 404 on every call. This bit a real user (2026-09-29:
82
+ > quickstart copy-paste + status lookups by artifact text instead of job_id).
83
+
84
+ ## Configuration
85
+
86
+ | Env Var | Constructor Arg | Description |
87
+ |---------|-----------------|-------------|
88
+ | `NOTARY_API_KEY` | `api_key` | API key for auth |
89
+ | `NOTARY_API_URL` | `base_url` | API base URL |
90
+ | `NOTARY_PUBKEY` | `notary_pubkey` | Notary Ed25519 public key (hex) |
91
+
92
+ ## Endpoints
93
+
94
+ | Method | Path | Description |
95
+ |--------|------|-------------|
96
+ | POST | `/v1/notarize` | Submit notarization |
97
+ | GET | `/v1/status/{job_id}` | Poll job status |
98
+ | GET | `/v1/pdr/{job_id}` | Get PDR |
99
+ | POST | `/v1/notarize/quote` | Get price quote |
100
+ | GET | `/.well-known/agent.json` | MCP Server Card |
101
+ | GET | `/openapi.json` | OpenAPI 3.0 spec |
102
+
103
+ ## PDR Verification
104
+
105
+ ```python
106
+ pdr = await client.get_pdr(job_id)
107
+ is_valid = pdr.verify(notary_pubkey_hex="9c7d64bb...")
108
+ ```
109
+
110
+ Uses NEP-413 raw-buffer verification. No SHA256 pre-hash.
111
+
112
+ ## Error Handling
113
+
114
+ ```python
115
+ from agent_notary.exceptions import (
116
+ NotaryAuthError, # 401 (authenticated CI surfaces; anonymous x402 gives 402 instead)
117
+ NotaryPaymentError, # 402 x402-challenge / 403
118
+ NotaryNotFoundError, # 404
119
+ NotaryValidationError, # 400/422 — invalid params, INVALID_AGENT_SIGNATURE
120
+ NotaryConflictError, # 409 — tx_hash/job_id already used (idempotency)
121
+ NotaryServerError, # 5xx, carries status_code
122
+ NotaryError, # base — safety net
123
+ )
124
+
125
+ try:
126
+ result = await client.notarize(req)
127
+ except NotaryAuthError:
128
+ print("Invalid API key")
129
+ except NotaryPaymentError as e:
130
+ print(f"Payment/sig failed: {e}")
131
+ except NotaryValidationError as e:
132
+ print(f"Bad request or invalid signature: {e}")
133
+ except NotaryConflictError:
134
+ print("This tx_hash/job_id was already used — generate a new one")
135
+ except NotaryError as e:
136
+ print(f"Other SDK error: {e}")
137
+ ```
138
+
139
+ ## Development
140
+
141
+ ```bash
142
+ pip install -e ".[dev]"
143
+ pytest tests/ -v
144
+ ```
145
+
146
+ ## Payment binding (NEAR rail)
147
+
148
+ NEAR payments must be provably linked to the account that sent them. Pass
149
+ `payer_account` (the NEAR account that paid) together with `bind_sig` — a
150
+ base64 Ed25519 **NEP-413 raw-buffer** signature (no SHA-256 pre-hash) over:
151
+
152
+ ```
153
+ AOTrust AITP-BIND v1
154
+ work_hash:<64 hex>
155
+ tx_hash:<tx hash exactly as submitted>
156
+ ```
157
+
158
+ The server resolves the account's public key via RPC and checks
159
+ `transaction.signer_id == payer_account`; a mismatch is rejected with
160
+ `403 PAYMENT_BIND_SIGNER_MISMATCH`, a bad signature with
161
+ `403 PAYMENT_BIND_INVALID`.
162
+
163
+ If `NEAR_PRIVATE_KEY` is set in the environment (or `near_private_key=` is
164
+ passed), the SDK signs the canonical message itself and fills `bind_sig`:
165
+
166
+ ```python
167
+ req = NotarizeRequest(tx_hash=tx, work_hash=wh)
168
+ await client.notarize(req, payer_account="alice.near")
169
+ ```
170
+
171
+ Server mode `AITP_BIND_MODE=warn` (default) accepts unbound requests with a
172
+ `PAYMENT_UNBOUND` log line; `enforce` requires the fields.
@@ -1,6 +1,6 @@
1
1
  """A&O Trust Layer — Agent Notary SDK (asyncio-native)."""
2
2
 
3
- __version__ = "2.3.6"
3
+ __version__ = "2.3.7"
4
4
 
5
5
  from .client import NotaryClient
6
6
  from .models import NotarizeRequest, NotarizeResult, PdrResult, StatusResult
@@ -9,12 +9,48 @@ from typing import Optional
9
9
  import aiohttp
10
10
 
11
11
  from .models import NotarizeRequest, NotarizeResult, StatusResult, PdrResult
12
+ NEP413_TAG = 2147484061
13
+
14
+
15
+ def canonical_bind_message(work_hash: str, tx_hash: str) -> bytes:
16
+ """Canonical AOTrust AITP-BIND v1 message — identical to the server module."""
17
+ return f"AOTrust AITP-BIND v1\nwork_hash:{work_hash}\ntx_hash:{tx_hash}".encode("utf-8")
18
+
19
+
20
+ def _decode_ed25519_key(raw: str) -> bytes:
21
+ """Accepts hex, base58, or NEAR's 'ed25519:<base58>' key format."""
22
+ import base58
23
+ key = raw.strip()
24
+ if key.startswith("ed25519:"):
25
+ key = key.split(":", 1)[1]
26
+ try:
27
+ if len(key) == 64 and all(c in "0123456789abcdefABCDEF" for c in key):
28
+ return bytes.fromhex(key)
29
+ except Exception:
30
+ pass
31
+ return base58.b58decode(key)
32
+
33
+
34
+ def _sign_bind_message(private_key: str, work_hash: str, tx_hash: str) -> str:
35
+ """NEP-413 RAW buffer signature (no SHA-256 pre-hash) over the bind message."""
36
+ import base64
37
+ import struct
38
+ from nacl.signing import SigningKey
39
+
40
+ msg = canonical_bind_message(work_hash, tx_hash)
41
+ buf = struct.pack("<II", NEP413_TAG, len(msg)) + msg
42
+ sk = SigningKey(_decode_ed25519_key(private_key))
43
+ return base64.b64encode(sk.sign(buf).signature).decode()
44
+
45
+
12
46
  from .exceptions import (
13
47
  NotaryError,
14
48
  NotaryAuthError,
15
49
  NotaryNotFoundError,
16
50
  NotaryPaymentError,
17
51
  NotaryServerError,
52
+ NotaryValidationError,
53
+ NotaryConflictError,
18
54
  )
19
55
 
20
56
  DEFAULT_TIMEOUT = 30
@@ -119,22 +155,50 @@ class NotaryClient:
119
155
  raise NotaryNotFoundError(body.get("error", "Not found"))
120
156
  if status == 429:
121
157
  raise NotaryError(body.get("error", body.get("message", "Rate limit reached")))
158
+ if status == 409:
159
+ raise NotaryConflictError(body.get("error", body.get("message", "Conflict")))
160
+ if status in (400, 422):
161
+ detail = body.get("message") or body.get("detail") or body.get("error") or f"Invalid request ({status})"
162
+ raise NotaryValidationError(f"{detail} (HTTP {status})")
122
163
  if status >= 500:
123
164
  raise NotaryServerError(body.get("error", f"Server error {status}"), status_code=status)
124
165
 
125
- async def notarize(self, request: NotarizeRequest) -> NotarizeResult:
166
+ async def notarize(
167
+ self,
168
+ request: NotarizeRequest,
169
+ payer_account: Optional[str] = None,
170
+ bind_sig: Optional[str] = None,
171
+ near_private_key: Optional[str] = None,
172
+ ) -> NotarizeResult:
126
173
  """POST /notarize — anonymous x402 path.
127
174
 
128
175
  Without payment the server answers 402 with the x402 challenge
129
176
  (x402Version/accepts[0].resource=...); NotaryPaymentError carries it.
130
177
  Payload built for the live x402 contract: work_hash at top level
131
178
  (the CI-channel envelope with tx_hash/payload.work_hash is NOT used here).
179
+
180
+ Payment binding (V5-A', NEAR rail): pass payer_account + bind_sig, or
181
+ let the SDK build them. If `near_private_key` (or env NEAR_PRIVATE_KEY)
182
+ is set and payer_account is provided, the SDK signs the canonical
183
+ AOTrust AITP-BIND v1 message with the NEP-413 raw buffer and fills
184
+ bind_sig. Without a key, fields are simply not added (server warn mode
185
+ passes unbound requests).
132
186
  """
187
+ payer = payer_account or request.payer_account
188
+ sig = bind_sig or request.bind_sig
189
+ key = near_private_key or os.environ.get("NEAR_PRIVATE_KEY")
190
+ if not sig and key and payer and request.work_hash and request.tx_hash:
191
+ sig = _sign_bind_message(key, request.work_hash, request.tx_hash)
192
+
133
193
  payload: dict = {"work_hash": request.work_hash or ""}
134
194
  if request.agent_sig:
135
195
  payload["agent_sig"] = request.agent_sig
136
196
  if request.agent_pubkey:
137
197
  payload["agent_pubkey"] = request.agent_pubkey
198
+ if payer:
199
+ payload["payer_account"] = payer
200
+ if sig:
201
+ payload["bind_sig"] = sig
138
202
  status, body = await self._request("POST", "/notarize", json_data=payload)
139
203
  self._check_status(status, body)
140
204
  if status not in (200, 202):
@@ -22,4 +22,12 @@ class NotaryServerError(NotaryError):
22
22
 
23
23
  def __init__(self, message: str, status_code: int = 0):
24
24
  super().__init__(message)
25
- self.status_code = status_code
25
+ self.status_code = status_code
26
+
27
+
28
+ class NotaryValidationError(NotaryError):
29
+ """400/422 — request payload rejected: invalid params or signature/content mismatch."""
30
+
31
+
32
+ class NotaryConflictError(NotaryError):
33
+ """409 — idempotency conflict: this tx_hash/job_id was already used."""
@@ -33,6 +33,13 @@ class NotarizeRequest(BaseModel):
33
33
  work_hash: Optional[str] = Field(None, description="SHA-256 hex digest (64 chars)")
34
34
  agent_sig: Optional[str] = Field(None, description="Base64 Ed25519 signature (sig_A)")
35
35
  agent_pubkey: Optional[str] = Field(None, description="Hex Ed25519 public key")
36
+ payer_account: Optional[str] = Field(
37
+ None, description="NEAR account that paid/signed the tx (payment binding)"
38
+ )
39
+ bind_sig: Optional[str] = Field(
40
+ None,
41
+ description="Base64 Ed25519 NEP-413 signature over the AOTrust AITP-BIND v1 message",
42
+ )
36
43
 
37
44
  def to_api_payload(self) -> dict:
38
45
  payload: dict = {"tx_hash": self.tx_hash}
@@ -42,6 +49,10 @@ class NotarizeRequest(BaseModel):
42
49
  payload["agent_sig"] = self.agent_sig
43
50
  if self.agent_pubkey:
44
51
  payload["agent_pubkey"] = self.agent_pubkey
52
+ if self.payer_account:
53
+ payload["payer_account"] = self.payer_account
54
+ if self.bind_sig:
55
+ payload["bind_sig"] = self.bind_sig
45
56
  return payload
46
57
 
47
58
 
@@ -0,0 +1,199 @@
1
+ Metadata-Version: 2.4
2
+ Name: aotrust-protocol
3
+ Version: 2.4.0
4
+ Summary: Asyncio-native SDK for A&O Trust Layer — Agent Notary Service
5
+ Author: A&O Trust Layer
6
+ License: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Topic :: Security :: Cryptography
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: aiohttp>=3.9
19
+ Requires-Dist: pydantic>=2.0
20
+ Requires-Dist: pynacl>=1.5
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.0; extra == "dev"
23
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
24
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
25
+ Requires-Dist: aioresponses>=0.7; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # aotrust-protocol SDK
29
+
30
+ <!-- mcp-name: link.aotrust/notary -->
31
+
32
+ Asyncio-native Python SDK for the AOTrust Notary API — cryptographic proof-of-existence for AI agent outputs.
33
+
34
+ > **Why protocol-level proofs?** An ERC-8004 empirical study (2026) measured that **>98% of agent feedback records carry no proof of an underlying interaction** (no payment proof, no task linkage); 59–91% of reviewers on three chains participated in coordinated Sybil campaigns, and manipulating a reputation score past a trust threshold costs as little as **$0.0027**. The agentic economy is moving from *statements* to *verifiable claims*: a PDR (239 bytes, Ed25519-signed, hash-chained, NEAR-anchored) turns any output or payment into independent evidence — verifiable by anyone, without trusting the provider's database.
35
+ > *(Refs: arXiv:2610.12948 [cs.CR], 2026; ERC-8004 Sybil measurements, Xiong et al. 2026.)*
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pip install aotrust-protocol
41
+ ```
42
+
43
+ ## Quickstart
44
+
45
+ ### Free tier — no API key, no wallet (fastest way to try)
46
+
47
+ ```python
48
+ import asyncio, hashlib
49
+ from agent_notary import NotaryClient
50
+
51
+ async def main():
52
+ # IMPORTANT: no /v1 suffix — the SDK appends API paths itself.
53
+ client = NotaryClient(base_url="https://api.aotrust.link")
54
+
55
+ work_hash = hashlib.sha256(b"my artifact text").hexdigest()
56
+ result = await client.shield_free(work_hash)
57
+ job_id = result["job_id"] # keep this UUID — it is the handle
58
+ print("job_id:", job_id)
59
+
60
+ # Check status BY job_id (not by the artifact text!)
61
+ status = await client.get_status(job_id)
62
+ print("status:", status.status) # PENDING → anchored (anchor batches)
63
+
64
+ # Fetch + verify the PDR
65
+ pdr = await client.get_pdr(job_id)
66
+ print("verify in browser:", pdr.verify_url)
67
+ assert (await client.verify_pdr(pdr.pdr_b64))["valid"]
68
+
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ Free tier: 5 PDR / 24h per IP. Examples: [`examples/04_free_tier.py`](examples/04_free_tier.py).
73
+
74
+ ### Witness mode (pre-paid tx_hash)
75
+
76
+ ```python
77
+ import asyncio
78
+ from agent_notary import NotaryClient, NotarizeRequest
79
+
80
+ async def main():
81
+ client = NotaryClient(
82
+ api_key="your-api-key",
83
+ base_url="https://api.aotrust.link" # no /v1 suffix!
84
+ )
85
+
86
+ # Submit notarization
87
+ req = NotarizeRequest(
88
+ tx_hash="EzrfDW5b...",
89
+ work_hash="599d6999...",
90
+ agent_sig="base64_sig_A...",
91
+ agent_pubkey="aff91a18...",
92
+ )
93
+ result = await client.notarize(req)
94
+ print(f"Job: {result.job_id}")
95
+
96
+ # Poll for PDR — status/pdr calls always take the job_id returned above
97
+ status = await client.wait_for_pdr(result.job_id, timeout=60)
98
+ if status.is_anchored:
99
+ pdr = await client.get_pdr(result.job_id)
100
+ assert pdr.verify(client.notary_pubkey)
101
+ print("PDR Valid: YES")
102
+
103
+ asyncio.run(main())
104
+ ```
105
+
106
+ > **Why no `/v1`?** `base_url` must be `https://api.aotrust.link`. The SDK
107
+ > already builds `/v1/...` request paths; adding `/v1` yourself produces
108
+ > `/v1/v1/...` → HTTP 404 on every call. This bit a real user (2026-09-29:
109
+ > quickstart copy-paste + status lookups by artifact text instead of job_id).
110
+
111
+ ## Configuration
112
+
113
+ | Env Var | Constructor Arg | Description |
114
+ |---------|-----------------|-------------|
115
+ | `NOTARY_API_KEY` | `api_key` | API key for auth |
116
+ | `NOTARY_API_URL` | `base_url` | API base URL |
117
+ | `NOTARY_PUBKEY` | `notary_pubkey` | Notary Ed25519 public key (hex) |
118
+
119
+ ## Endpoints
120
+
121
+ | Method | Path | Description |
122
+ |--------|------|-------------|
123
+ | POST | `/v1/notarize` | Submit notarization |
124
+ | GET | `/v1/status/{job_id}` | Poll job status |
125
+ | GET | `/v1/pdr/{job_id}` | Get PDR |
126
+ | POST | `/v1/notarize/quote` | Get price quote |
127
+ | GET | `/.well-known/agent.json` | MCP Server Card |
128
+ | GET | `/openapi.json` | OpenAPI 3.0 spec |
129
+
130
+ ## PDR Verification
131
+
132
+ ```python
133
+ pdr = await client.get_pdr(job_id)
134
+ is_valid = pdr.verify(notary_pubkey_hex="9c7d64bb...")
135
+ ```
136
+
137
+ Uses NEP-413 raw-buffer verification. No SHA256 pre-hash.
138
+
139
+ ## Error Handling
140
+
141
+ ```python
142
+ from agent_notary.exceptions import (
143
+ NotaryAuthError, # 401 (authenticated CI surfaces; anonymous x402 gives 402 instead)
144
+ NotaryPaymentError, # 402 x402-challenge / 403
145
+ NotaryNotFoundError, # 404
146
+ NotaryValidationError, # 400/422 — invalid params, INVALID_AGENT_SIGNATURE
147
+ NotaryConflictError, # 409 — tx_hash/job_id already used (idempotency)
148
+ NotaryServerError, # 5xx, carries status_code
149
+ NotaryError, # base — safety net
150
+ )
151
+
152
+ try:
153
+ result = await client.notarize(req)
154
+ except NotaryAuthError:
155
+ print("Invalid API key")
156
+ except NotaryPaymentError as e:
157
+ print(f"Payment/sig failed: {e}")
158
+ except NotaryValidationError as e:
159
+ print(f"Bad request or invalid signature: {e}")
160
+ except NotaryConflictError:
161
+ print("This tx_hash/job_id was already used — generate a new one")
162
+ except NotaryError as e:
163
+ print(f"Other SDK error: {e}")
164
+ ```
165
+
166
+ ## Development
167
+
168
+ ```bash
169
+ pip install -e ".[dev]"
170
+ pytest tests/ -v
171
+ ```
172
+
173
+ ## Payment binding (NEAR rail)
174
+
175
+ NEAR payments must be provably linked to the account that sent them. Pass
176
+ `payer_account` (the NEAR account that paid) together with `bind_sig` — a
177
+ base64 Ed25519 **NEP-413 raw-buffer** signature (no SHA-256 pre-hash) over:
178
+
179
+ ```
180
+ AOTrust AITP-BIND v1
181
+ work_hash:<64 hex>
182
+ tx_hash:<tx hash exactly as submitted>
183
+ ```
184
+
185
+ The server resolves the account's public key via RPC and checks
186
+ `transaction.signer_id == payer_account`; a mismatch is rejected with
187
+ `403 PAYMENT_BIND_SIGNER_MISMATCH`, a bad signature with
188
+ `403 PAYMENT_BIND_INVALID`.
189
+
190
+ If `NEAR_PRIVATE_KEY` is set in the environment (or `near_private_key=` is
191
+ passed), the SDK signs the canonical message itself and fills `bind_sig`:
192
+
193
+ ```python
194
+ req = NotarizeRequest(tx_hash=tx, work_hash=wh)
195
+ await client.notarize(req, payer_account="alice.near")
196
+ ```
197
+
198
+ Server mode `AITP_BIND_MODE=warn` (default) accepts unbound requests with a
199
+ `PAYMENT_UNBOUND` log line; `enforce` requires the fields.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aotrust-protocol"
7
- version = "2.3.6"
7
+ version = "2.4.0"
8
8
  description = "Asyncio-native SDK for A&O Trust Layer — Agent Notary Service"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,118 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: aotrust-protocol
3
- Version: 2.3.6
4
- Summary: Asyncio-native SDK for A&O Trust Layer — Agent Notary Service
5
- Author: A&O Trust Layer
6
- License: MIT
7
- Classifier: Development Status :: 4 - Beta
8
- Classifier: Intended Audience :: Developers
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.10
11
- Classifier: Programming Language :: Python :: 3.11
12
- Classifier: Programming Language :: Python :: 3.12
13
- Classifier: Framework :: AsyncIO
14
- Classifier: Topic :: Security :: Cryptography
15
- Requires-Python: >=3.10
16
- Description-Content-Type: text/markdown
17
- License-File: LICENSE
18
- Requires-Dist: aiohttp>=3.9
19
- Requires-Dist: pydantic>=2.0
20
- Requires-Dist: pynacl>=1.5
21
- Provides-Extra: dev
22
- Requires-Dist: pytest>=7.0; extra == "dev"
23
- Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
24
- Requires-Dist: pytest-cov>=4.0; extra == "dev"
25
- Requires-Dist: aioresponses>=0.7; extra == "dev"
26
- Dynamic: license-file
27
-
28
- # aotrust-protocol SDK
29
-
30
- <!-- mcp-name: link.aotrust/notary -->
31
-
32
- Asyncio-native Python SDK for the AOTrust Notary API — cryptographic proof-of-existence for AI agent outputs.
33
-
34
- ## Install
35
-
36
- ```bash
37
- pip install aotrust-protocol
38
- ```
39
-
40
- ## Quickstart
41
-
42
- ```python
43
- import asyncio
44
- from agent_notary import NotaryClient, NotarizeRequest
45
-
46
- async def main():
47
- client = NotaryClient(
48
- api_key="your-api-key",
49
- base_url="https://api.aotrust.link/v1"
50
- )
51
-
52
- # Submit notarization
53
- req = NotarizeRequest(
54
- tx_hash="EzrfDW5b...",
55
- work_hash="599d6999...",
56
- agent_sig="base64_sig_A...",
57
- agent_pubkey="aff91a18...",
58
- )
59
- result = await client.notarize(req)
60
- print(f"Job: {result.job_id}")
61
-
62
- # Poll for PDR
63
- status = await client.wait_for_pdr(result.job_id, timeout=60)
64
- if status.is_anchored:
65
- pdr = await client.get_pdr(result.job_id)
66
- assert pdr.verify(client.notary_pubkey)
67
- print("PDR Valid: YES")
68
-
69
- asyncio.run(main())
70
- ```
71
-
72
- ## Configuration
73
-
74
- | Env Var | Constructor Arg | Description |
75
- |---------|-----------------|-------------|
76
- | `NOTARY_API_KEY` | `api_key` | API key for auth |
77
- | `NOTARY_API_URL` | `base_url` | API base URL |
78
- | `NOTARY_PUBKEY` | `notary_pubkey` | Notary Ed25519 public key (hex) |
79
-
80
- ## Endpoints
81
-
82
- | Method | Path | Description |
83
- |--------|------|-------------|
84
- | POST | `/v1/notarize` | Submit notarization |
85
- | GET | `/v1/status/{job_id}` | Poll job status |
86
- | GET | `/v1/pdr/{job_id}` | Get PDR |
87
- | POST | `/v1/notarize/quote` | Get price quote |
88
- | GET | `/.well-known/agent.json` | MCP Server Card |
89
- | GET | `/openapi.json` | OpenAPI 3.0 spec |
90
-
91
- ## PDR Verification
92
-
93
- ```python
94
- pdr = await client.get_pdr(job_id)
95
- is_valid = pdr.verify(notary_pubkey_hex="9c7d64bb...")
96
- ```
97
-
98
- Uses NEP-413 raw-buffer verification. No SHA256 pre-hash.
99
-
100
- ## Error Handling
101
-
102
- ```python
103
- from agent_notary.exceptions import NotaryAuthError, NotaryPaymentError, NotaryNotFoundError
104
-
105
- try:
106
- result = await client.notarize(req)
107
- except NotaryAuthError:
108
- print("Invalid API key")
109
- except NotaryPaymentError as e:
110
- print(f"Payment/sig failed: {e}")
111
- ```
112
-
113
- ## Development
114
-
115
- ```bash
116
- pip install -e ".[dev]"
117
- pytest tests/ -v
118
- ```
@@ -1,91 +0,0 @@
1
- # aotrust-protocol SDK
2
-
3
- <!-- mcp-name: link.aotrust/notary -->
4
-
5
- Asyncio-native Python SDK for the AOTrust Notary API — cryptographic proof-of-existence for AI agent outputs.
6
-
7
- ## Install
8
-
9
- ```bash
10
- pip install aotrust-protocol
11
- ```
12
-
13
- ## Quickstart
14
-
15
- ```python
16
- import asyncio
17
- from agent_notary import NotaryClient, NotarizeRequest
18
-
19
- async def main():
20
- client = NotaryClient(
21
- api_key="your-api-key",
22
- base_url="https://api.aotrust.link/v1"
23
- )
24
-
25
- # Submit notarization
26
- req = NotarizeRequest(
27
- tx_hash="EzrfDW5b...",
28
- work_hash="599d6999...",
29
- agent_sig="base64_sig_A...",
30
- agent_pubkey="aff91a18...",
31
- )
32
- result = await client.notarize(req)
33
- print(f"Job: {result.job_id}")
34
-
35
- # Poll for PDR
36
- status = await client.wait_for_pdr(result.job_id, timeout=60)
37
- if status.is_anchored:
38
- pdr = await client.get_pdr(result.job_id)
39
- assert pdr.verify(client.notary_pubkey)
40
- print("PDR Valid: YES")
41
-
42
- asyncio.run(main())
43
- ```
44
-
45
- ## Configuration
46
-
47
- | Env Var | Constructor Arg | Description |
48
- |---------|-----------------|-------------|
49
- | `NOTARY_API_KEY` | `api_key` | API key for auth |
50
- | `NOTARY_API_URL` | `base_url` | API base URL |
51
- | `NOTARY_PUBKEY` | `notary_pubkey` | Notary Ed25519 public key (hex) |
52
-
53
- ## Endpoints
54
-
55
- | Method | Path | Description |
56
- |--------|------|-------------|
57
- | POST | `/v1/notarize` | Submit notarization |
58
- | GET | `/v1/status/{job_id}` | Poll job status |
59
- | GET | `/v1/pdr/{job_id}` | Get PDR |
60
- | POST | `/v1/notarize/quote` | Get price quote |
61
- | GET | `/.well-known/agent.json` | MCP Server Card |
62
- | GET | `/openapi.json` | OpenAPI 3.0 spec |
63
-
64
- ## PDR Verification
65
-
66
- ```python
67
- pdr = await client.get_pdr(job_id)
68
- is_valid = pdr.verify(notary_pubkey_hex="9c7d64bb...")
69
- ```
70
-
71
- Uses NEP-413 raw-buffer verification. No SHA256 pre-hash.
72
-
73
- ## Error Handling
74
-
75
- ```python
76
- from agent_notary.exceptions import NotaryAuthError, NotaryPaymentError, NotaryNotFoundError
77
-
78
- try:
79
- result = await client.notarize(req)
80
- except NotaryAuthError:
81
- print("Invalid API key")
82
- except NotaryPaymentError as e:
83
- print(f"Payment/sig failed: {e}")
84
- ```
85
-
86
- ## Development
87
-
88
- ```bash
89
- pip install -e ".[dev]"
90
- pytest tests/ -v
91
- ```
@@ -1,118 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: aotrust-protocol
3
- Version: 2.3.6
4
- Summary: Asyncio-native SDK for A&O Trust Layer — Agent Notary Service
5
- Author: A&O Trust Layer
6
- License: MIT
7
- Classifier: Development Status :: 4 - Beta
8
- Classifier: Intended Audience :: Developers
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.10
11
- Classifier: Programming Language :: Python :: 3.11
12
- Classifier: Programming Language :: Python :: 3.12
13
- Classifier: Framework :: AsyncIO
14
- Classifier: Topic :: Security :: Cryptography
15
- Requires-Python: >=3.10
16
- Description-Content-Type: text/markdown
17
- License-File: LICENSE
18
- Requires-Dist: aiohttp>=3.9
19
- Requires-Dist: pydantic>=2.0
20
- Requires-Dist: pynacl>=1.5
21
- Provides-Extra: dev
22
- Requires-Dist: pytest>=7.0; extra == "dev"
23
- Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
24
- Requires-Dist: pytest-cov>=4.0; extra == "dev"
25
- Requires-Dist: aioresponses>=0.7; extra == "dev"
26
- Dynamic: license-file
27
-
28
- # aotrust-protocol SDK
29
-
30
- <!-- mcp-name: link.aotrust/notary -->
31
-
32
- Asyncio-native Python SDK for the AOTrust Notary API — cryptographic proof-of-existence for AI agent outputs.
33
-
34
- ## Install
35
-
36
- ```bash
37
- pip install aotrust-protocol
38
- ```
39
-
40
- ## Quickstart
41
-
42
- ```python
43
- import asyncio
44
- from agent_notary import NotaryClient, NotarizeRequest
45
-
46
- async def main():
47
- client = NotaryClient(
48
- api_key="your-api-key",
49
- base_url="https://api.aotrust.link/v1"
50
- )
51
-
52
- # Submit notarization
53
- req = NotarizeRequest(
54
- tx_hash="EzrfDW5b...",
55
- work_hash="599d6999...",
56
- agent_sig="base64_sig_A...",
57
- agent_pubkey="aff91a18...",
58
- )
59
- result = await client.notarize(req)
60
- print(f"Job: {result.job_id}")
61
-
62
- # Poll for PDR
63
- status = await client.wait_for_pdr(result.job_id, timeout=60)
64
- if status.is_anchored:
65
- pdr = await client.get_pdr(result.job_id)
66
- assert pdr.verify(client.notary_pubkey)
67
- print("PDR Valid: YES")
68
-
69
- asyncio.run(main())
70
- ```
71
-
72
- ## Configuration
73
-
74
- | Env Var | Constructor Arg | Description |
75
- |---------|-----------------|-------------|
76
- | `NOTARY_API_KEY` | `api_key` | API key for auth |
77
- | `NOTARY_API_URL` | `base_url` | API base URL |
78
- | `NOTARY_PUBKEY` | `notary_pubkey` | Notary Ed25519 public key (hex) |
79
-
80
- ## Endpoints
81
-
82
- | Method | Path | Description |
83
- |--------|------|-------------|
84
- | POST | `/v1/notarize` | Submit notarization |
85
- | GET | `/v1/status/{job_id}` | Poll job status |
86
- | GET | `/v1/pdr/{job_id}` | Get PDR |
87
- | POST | `/v1/notarize/quote` | Get price quote |
88
- | GET | `/.well-known/agent.json` | MCP Server Card |
89
- | GET | `/openapi.json` | OpenAPI 3.0 spec |
90
-
91
- ## PDR Verification
92
-
93
- ```python
94
- pdr = await client.get_pdr(job_id)
95
- is_valid = pdr.verify(notary_pubkey_hex="9c7d64bb...")
96
- ```
97
-
98
- Uses NEP-413 raw-buffer verification. No SHA256 pre-hash.
99
-
100
- ## Error Handling
101
-
102
- ```python
103
- from agent_notary.exceptions import NotaryAuthError, NotaryPaymentError, NotaryNotFoundError
104
-
105
- try:
106
- result = await client.notarize(req)
107
- except NotaryAuthError:
108
- print("Invalid API key")
109
- except NotaryPaymentError as e:
110
- print(f"Payment/sig failed: {e}")
111
- ```
112
-
113
- ## Development
114
-
115
- ```bash
116
- pip install -e ".[dev]"
117
- pytest tests/ -v
118
- ```