aotrust-protocol 2.3.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aotrust-protocol
3
- Version: 2.3.7
3
+ Version: 2.4.0
4
4
  Summary: Asyncio-native SDK for A&O Trust Layer — Agent Notary Service
5
5
  Author: A&O Trust Layer
6
6
  License: MIT
@@ -31,6 +31,9 @@ Dynamic: license-file
31
31
 
32
32
  Asyncio-native Python SDK for the AOTrust Notary API — cryptographic proof-of-existence for AI agent outputs.
33
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
+
34
37
  ## Install
35
38
 
36
39
  ```bash
@@ -166,3 +169,31 @@ except NotaryError as e:
166
169
  pip install -e ".[dev]"
167
170
  pytest tests/ -v
168
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,6 +4,9 @@
4
4
 
5
5
  Asyncio-native Python SDK for the AOTrust Notary API — cryptographic proof-of-existence for AI agent outputs.
6
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
+
7
10
  ## Install
8
11
 
9
12
  ```bash
@@ -138,4 +141,32 @@ except NotaryError as e:
138
141
  ```bash
139
142
  pip install -e ".[dev]"
140
143
  pytest tests/ -v
141
- ```
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.
@@ -9,6 +9,40 @@ 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,
@@ -129,19 +163,42 @@ class NotaryClient:
129
163
  if status >= 500:
130
164
  raise NotaryServerError(body.get("error", f"Server error {status}"), status_code=status)
131
165
 
132
- 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:
133
173
  """POST /notarize — anonymous x402 path.
134
174
 
135
175
  Without payment the server answers 402 with the x402 challenge
136
176
  (x402Version/accepts[0].resource=...); NotaryPaymentError carries it.
137
177
  Payload built for the live x402 contract: work_hash at top level
138
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).
139
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
+
140
193
  payload: dict = {"work_hash": request.work_hash or ""}
141
194
  if request.agent_sig:
142
195
  payload["agent_sig"] = request.agent_sig
143
196
  if request.agent_pubkey:
144
197
  payload["agent_pubkey"] = request.agent_pubkey
198
+ if payer:
199
+ payload["payer_account"] = payer
200
+ if sig:
201
+ payload["bind_sig"] = sig
145
202
  status, body = await self._request("POST", "/notarize", json_data=payload)
146
203
  self._check_status(status, body)
147
204
  if status not in (200, 202):
@@ -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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aotrust-protocol
3
- Version: 2.3.7
3
+ Version: 2.4.0
4
4
  Summary: Asyncio-native SDK for A&O Trust Layer — Agent Notary Service
5
5
  Author: A&O Trust Layer
6
6
  License: MIT
@@ -31,6 +31,9 @@ Dynamic: license-file
31
31
 
32
32
  Asyncio-native Python SDK for the AOTrust Notary API — cryptographic proof-of-existence for AI agent outputs.
33
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
+
34
37
  ## Install
35
38
 
36
39
  ```bash
@@ -166,3 +169,31 @@ except NotaryError as e:
166
169
  pip install -e ".[dev]"
167
170
  pytest tests/ -v
168
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.7"
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"