agentguard-python-sdk 0.2.2__tar.gz → 0.2.4__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.
Files changed (22) hide show
  1. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/LICENSE +21 -21
  2. {agentguard_python_sdk-0.2.2/agentguard_python_sdk.egg-info → agentguard_python_sdk-0.2.4}/PKG-INFO +1 -1
  3. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard/__init__.py +57 -57
  4. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard/auth.py +20 -20
  5. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard/client.py +2 -1
  6. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard/config.py +1 -1
  7. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard/observability.py +52 -52
  8. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard_crypto/__init__.py +1 -1
  9. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard_crypto/canonical.py +34 -34
  10. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard_crypto/models.py +108 -108
  11. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard_crypto/signing.py +76 -76
  12. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4/agentguard_python_sdk.egg-info}/PKG-INFO +1 -1
  13. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/pyproject.toml +1 -1
  14. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/README.md +0 -0
  15. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard/consent.py +0 -0
  16. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard/errors.py +0 -0
  17. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard/types.py +0 -0
  18. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard_python_sdk.egg-info/SOURCES.txt +0 -0
  19. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard_python_sdk.egg-info/dependency_links.txt +0 -0
  20. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard_python_sdk.egg-info/requires.txt +0 -0
  21. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/agentguard_python_sdk.egg-info/top_level.txt +0 -0
  22. {agentguard_python_sdk-0.2.2 → agentguard_python_sdk-0.2.4}/setup.cfg +0 -0
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 AgentGuard Team
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AgentGuard Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentguard-python-sdk
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A production-grade middleware for AI agents to perform on-chain payments and verifiable consent.
5
5
  Author: AgentGuard Team
6
6
  License-Expression: MIT
@@ -1,57 +1,57 @@
1
- from .client import AgentGuardClient
2
- from .config import AgentGuardConfig
3
- from .errors import (
4
- AgentGuardError,
5
- ValidationError,
6
- TransportError,
7
- AuthenticationError,
8
- InvalidSignatureError,
9
- ExpiredSignatureError,
10
- PaymentError,
11
- ReplayAttackError,
12
- DuplicatePaymentError,
13
- BudgetExceededError,
14
- VerificationError,
15
- SecurityError,
16
- UnknownExecutionStateError,
17
- AuthorizationRequiredError
18
- )
19
- from .types import (
20
- AgentGuardReceipt,
21
- AuditProof,
22
- AuthorizationConstraints,
23
- NonceContext,
24
- AuthorizationMetadata,
25
- DelegatedAuthorization,
26
- AuthorizationReceipt,
27
- RevocationReceipt
28
- )
29
-
30
- __version__ = "0.2.2"
31
-
32
- __all__ = [
33
- "AgentGuardClient",
34
- "AgentGuardConfig",
35
- "AgentGuardError",
36
- "ValidationError",
37
- "TransportError",
38
- "AuthenticationError",
39
- "InvalidSignatureError",
40
- "ExpiredSignatureError",
41
- "PaymentError",
42
- "ReplayAttackError",
43
- "DuplicatePaymentError",
44
- "BudgetExceededError",
45
- "VerificationError",
46
- "SecurityError",
47
- "UnknownExecutionStateError",
48
- "AuthorizationRequiredError",
49
- "AgentGuardReceipt",
50
- "AuditProof",
51
- "AuthorizationConstraints",
52
- "NonceContext",
53
- "AuthorizationMetadata",
54
- "DelegatedAuthorization",
55
- "AuthorizationReceipt",
56
- "RevocationReceipt",
57
- ]
1
+ from .client import AgentGuardClient
2
+ from .config import AgentGuardConfig
3
+ from .errors import (
4
+ AgentGuardError,
5
+ ValidationError,
6
+ TransportError,
7
+ AuthenticationError,
8
+ InvalidSignatureError,
9
+ ExpiredSignatureError,
10
+ PaymentError,
11
+ ReplayAttackError,
12
+ DuplicatePaymentError,
13
+ BudgetExceededError,
14
+ VerificationError,
15
+ SecurityError,
16
+ UnknownExecutionStateError,
17
+ AuthorizationRequiredError
18
+ )
19
+ from .types import (
20
+ AgentGuardReceipt,
21
+ AuditProof,
22
+ AuthorizationConstraints,
23
+ NonceContext,
24
+ AuthorizationMetadata,
25
+ DelegatedAuthorization,
26
+ AuthorizationReceipt,
27
+ RevocationReceipt
28
+ )
29
+
30
+ __version__ = "0.2.2"
31
+
32
+ __all__ = [
33
+ "AgentGuardClient",
34
+ "AgentGuardConfig",
35
+ "AgentGuardError",
36
+ "ValidationError",
37
+ "TransportError",
38
+ "AuthenticationError",
39
+ "InvalidSignatureError",
40
+ "ExpiredSignatureError",
41
+ "PaymentError",
42
+ "ReplayAttackError",
43
+ "DuplicatePaymentError",
44
+ "BudgetExceededError",
45
+ "VerificationError",
46
+ "SecurityError",
47
+ "UnknownExecutionStateError",
48
+ "AuthorizationRequiredError",
49
+ "AgentGuardReceipt",
50
+ "AuditProof",
51
+ "AuthorizationConstraints",
52
+ "NonceContext",
53
+ "AuthorizationMetadata",
54
+ "DelegatedAuthorization",
55
+ "AuthorizationReceipt",
56
+ "RevocationReceipt",
57
+ ]
@@ -1,20 +1,20 @@
1
- import json
2
- import unicodedata
3
- import hashlib
4
- import base64
5
- import time
6
- from typing import Dict, Any
7
- import nacl.signing
8
- import nacl.encoding
9
-
10
- from agentguard_crypto.signing import sign_payload
11
-
12
- def sign_request(payload: Any, private_key_b64: str) -> str:
13
- """
14
- Signs a request using the shared cryptographic source of truth.
15
- Payload MUST be a strictly typed canonical model.
16
- """
17
- if isinstance(payload, dict):
18
- raise TypeError("Dictionary payloads are strictly prohibited. You must use a Canonical Payload model.")
19
-
20
- return sign_payload(payload, private_key_b64)
1
+ import json
2
+ import unicodedata
3
+ import hashlib
4
+ import base64
5
+ import time
6
+ from typing import Dict, Any
7
+ import nacl.signing
8
+ import nacl.encoding
9
+
10
+ from agentguard_crypto.signing import sign_payload
11
+
12
+ def sign_request(payload: Any, private_key_b64: str) -> str:
13
+ """
14
+ Signs a request using the shared cryptographic source of truth.
15
+ Payload MUST be a strictly typed canonical model.
16
+ """
17
+ if isinstance(payload, dict):
18
+ raise TypeError("Dictionary payloads are strictly prohibited. You must use a Canonical Payload model.")
19
+
20
+ return sign_payload(payload, private_key_b64)
@@ -209,7 +209,8 @@ class AgentGuardClient:
209
209
  receiver=header_receiver,
210
210
  fiduciary_name=self.config.fiduciary_name,
211
211
  fiduciary_version=self.config.fiduciary_version,
212
- hash_version=self.config.hash_version
212
+ hash_version=self.config.hash_version,
213
+ schema_version=1
213
214
  )
214
215
 
215
216
  # Phase 7: Strict Transport Contract — to_wire_dict() is the ONLY authority
@@ -25,7 +25,7 @@ class AgentGuardConfig(BaseSettings):
25
25
  tracing_enabled: bool = True
26
26
 
27
27
  # Validation Policy
28
- strict_receiver_validation: bool = True
28
+ strict_receiver_validation: bool = False
29
29
  trusted_receiver: Optional[str] = None
30
30
 
31
31
  # Fiduciary Identity Config
@@ -1,52 +1,52 @@
1
- import time
2
- import json
3
- import uuid
4
- import logging
5
- from datetime import datetime
6
- from typing import Optional, Dict, Any
7
-
8
- # [PRIORITY 9] SDK OBSERVABILITY CORE
9
-
10
- class Span:
11
- def __init__(self, name: str, trace_id: str, parent_id: Optional[str] = None):
12
- self.name = name
13
- self.trace_id = trace_id
14
- self.span_id = str(uuid.uuid4())[:8]
15
- self.parent_id = parent_id
16
- self.start_time = time.time()
17
- self.metadata = {}
18
-
19
- def set_attribute(self, key: str, value: Any):
20
- self.metadata[key] = value
21
-
22
- def end(self, error: Optional[str] = None):
23
- duration = (time.time() - self.start_time) * 1000
24
- log_data = {
25
- "timestamp": datetime.utcnow().isoformat() + "Z",
26
- "level": "INFO" if not error else "ERROR",
27
- "service": "agentguard-sdk",
28
- "event": "span_end",
29
- "span_name": self.name,
30
- "span_id": self.span_id,
31
- "parent_id": self.parent_id,
32
- "trace_id": self.trace_id,
33
- "duration_ms": duration,
34
- "error": error,
35
- "metadata": self.metadata
36
- }
37
- logging.getLogger("agentguard-sdk").info(json.dumps(log_data))
38
-
39
- def start_span(name: str, trace_id: str, parent_id: Optional[str] = None) -> Span:
40
- span = Span(name, trace_id, parent_id)
41
- log_data = {
42
- "timestamp": datetime.utcnow().isoformat() + "Z",
43
- "level": "INFO",
44
- "service": "agentguard-sdk",
45
- "event": "span_start",
46
- "span_name": name,
47
- "span_id": span.span_id,
48
- "parent_id": parent_id,
49
- "trace_id": trace_id
50
- }
51
- logging.getLogger("agentguard-sdk").info(json.dumps(log_data))
52
- return span
1
+ import time
2
+ import json
3
+ import uuid
4
+ import logging
5
+ from datetime import datetime
6
+ from typing import Optional, Dict, Any
7
+
8
+ # [PRIORITY 9] SDK OBSERVABILITY CORE
9
+
10
+ class Span:
11
+ def __init__(self, name: str, trace_id: str, parent_id: Optional[str] = None):
12
+ self.name = name
13
+ self.trace_id = trace_id
14
+ self.span_id = str(uuid.uuid4())[:8]
15
+ self.parent_id = parent_id
16
+ self.start_time = time.time()
17
+ self.metadata = {}
18
+
19
+ def set_attribute(self, key: str, value: Any):
20
+ self.metadata[key] = value
21
+
22
+ def end(self, error: Optional[str] = None):
23
+ duration = (time.time() - self.start_time) * 1000
24
+ log_data = {
25
+ "timestamp": datetime.utcnow().isoformat() + "Z",
26
+ "level": "INFO" if not error else "ERROR",
27
+ "service": "agentguard-sdk",
28
+ "event": "span_end",
29
+ "span_name": self.name,
30
+ "span_id": self.span_id,
31
+ "parent_id": self.parent_id,
32
+ "trace_id": self.trace_id,
33
+ "duration_ms": duration,
34
+ "error": error,
35
+ "metadata": self.metadata
36
+ }
37
+ logging.getLogger("agentguard-sdk").info(json.dumps(log_data))
38
+
39
+ def start_span(name: str, trace_id: str, parent_id: Optional[str] = None) -> Span:
40
+ span = Span(name, trace_id, parent_id)
41
+ log_data = {
42
+ "timestamp": datetime.utcnow().isoformat() + "Z",
43
+ "level": "INFO",
44
+ "service": "agentguard-sdk",
45
+ "event": "span_start",
46
+ "span_name": name,
47
+ "span_id": span.span_id,
48
+ "parent_id": parent_id,
49
+ "trace_id": trace_id
50
+ }
51
+ logging.getLogger("agentguard-sdk").info(json.dumps(log_data))
52
+ return span
@@ -1 +1 @@
1
- # Empty init for agentguard_crypto package
1
+ # Empty init for agentguard_crypto package
@@ -1,34 +1,34 @@
1
- import json
2
- import unicodedata
3
- from typing import Dict, Any
4
-
5
- def canonicalize_payload(payload: Dict[str, Any]) -> bytes:
6
- """
7
- Industry-Grade Deterministic Canonicalization.
8
- 1. Recursive NFC Normalization.
9
- 2. Recursive None-Stripping (Parity between null/unset).
10
- 3. Alphabetical Key Sorting.
11
- 4. Compact JSON (separators=(',', ':')).
12
- 5. UTF-8 Encoding.
13
- """
14
- def normalize_rec(obj):
15
- if isinstance(obj, str):
16
- # Strict NFC Normalization for inconsistent client environments
17
- return unicodedata.normalize("NFC", obj)
18
- if isinstance(obj, dict):
19
- # Sort keys and discard None values for cryptographic parity
20
- return {normalize_rec(k): normalize_rec(v) for k, v in obj.items() if v is not None}
21
- if isinstance(obj, list):
22
- return [normalize_rec(i) for i in obj]
23
- return obj
24
-
25
- norm_payload = normalize_rec(payload)
26
-
27
- # Deterministic JSON serialization
28
- canonical_str = json.dumps(
29
- norm_payload,
30
- sort_keys=True,
31
- separators=(",", ":"),
32
- ensure_ascii=False
33
- )
34
- return canonical_str.encode("utf-8")
1
+ import json
2
+ import unicodedata
3
+ from typing import Dict, Any
4
+
5
+ def canonicalize_payload(payload: Dict[str, Any]) -> bytes:
6
+ """
7
+ Industry-Grade Deterministic Canonicalization.
8
+ 1. Recursive NFC Normalization.
9
+ 2. Recursive None-Stripping (Parity between null/unset).
10
+ 3. Alphabetical Key Sorting.
11
+ 4. Compact JSON (separators=(',', ':')).
12
+ 5. UTF-8 Encoding.
13
+ """
14
+ def normalize_rec(obj):
15
+ if isinstance(obj, str):
16
+ # Strict NFC Normalization for inconsistent client environments
17
+ return unicodedata.normalize("NFC", obj)
18
+ if isinstance(obj, dict):
19
+ # Sort keys and discard None values for cryptographic parity
20
+ return {normalize_rec(k): normalize_rec(v) for k, v in obj.items() if v is not None}
21
+ if isinstance(obj, list):
22
+ return [normalize_rec(i) for i in obj]
23
+ return obj
24
+
25
+ norm_payload = normalize_rec(payload)
26
+
27
+ # Deterministic JSON serialization
28
+ canonical_str = json.dumps(
29
+ norm_payload,
30
+ sort_keys=True,
31
+ separators=(",", ":"),
32
+ ensure_ascii=False
33
+ )
34
+ return canonical_str.encode("utf-8")
@@ -1,108 +1,108 @@
1
- from typing import Optional
2
- from pydantic import BaseModel, Field, StrictInt
3
- from .canonical import canonicalize_payload
4
-
5
- SCHEMA_VERSION = 1
6
-
7
- class CanonicalPayloadMixin:
8
- """
9
- PROTOCOL LAYER: Single Serialization Authority.
10
-
11
- ALL serialization MUST derive from _serialize().
12
- Direct model_dump() calls are FORBIDDEN in protocol code.
13
-
14
- Invariant enforced by construction:
15
- wire_payload == signed_payload == verified_payload
16
- """
17
-
18
- def _serialize(self) -> dict:
19
- """SINGLE INTERNAL SERIALIZATION SOURCE.
20
- Every external serialization method MUST derive from this.
21
- Uses exclude_unset=True to prevent default injection drift."""
22
- return self.model_dump(exclude_unset=True)
23
-
24
- def to_canonical_bytes(self) -> bytes:
25
- """Returns deterministic canonical bytes for signing/verification."""
26
- return canonicalize_payload(self._serialize())
27
-
28
- def to_wire_dict(self) -> dict:
29
- """Returns the EXACT dict for HTTP transport.
30
- Guaranteed identical field set to what is signed/verified."""
31
- return self._serialize()
32
-
33
- # Backward compatibility alias (used by signing.py, consent.py, payment.py)
34
- get_canonical_bytes = to_canonical_bytes
35
-
36
- class CanonicalPaymentPayload(BaseModel, CanonicalPayloadMixin):
37
- """
38
- SINGLE SOURCE OF TRUTH (Phase 2).
39
- Represents the exact fields that are signed and verified for Payment.
40
- """
41
- # Core Identity
42
- principal_id: str
43
- resource_url: str
44
- purpose: str
45
- microalgos: StrictInt = Field(gt=0)
46
-
47
- # Target
48
- receiver_address: Optional[str] = None
49
- receiver: Optional[str] = None
50
-
51
- # Cryptographic Nonces
52
- nonce: str
53
- timestamp: int
54
-
55
- # Authorization Context
56
- authorization_id: Optional[str] = None
57
- consent_hash: str
58
-
59
- # Correlation & Idempotency
60
- idempotency_key: str
61
- correlation_id: str
62
- session_id: Optional[str] = None
63
-
64
- # Fiduciary Metadata (Phase A1)
65
- fiduciary_name: Optional[str] = None
66
- fiduciary_version: Optional[str] = None
67
- hash_version: Optional[int] = 1
68
-
69
- # Versioning (Phase 5)
70
- schema_version: int = Field(default=SCHEMA_VERSION)
71
-
72
-
73
- class CanonicalAuthorizationPayload(BaseModel, CanonicalPayloadMixin):
74
- """
75
- STRICTLY contains ONLY authorization lifecycle fields.
76
- """
77
- principal_id: str
78
- logic_sig_b64: str
79
- max_amount: StrictInt = Field(gt=0)
80
- expires_at_unix: StrictInt
81
- receiver: Optional[str] = None
82
- purpose_hash: Optional[str] = None
83
- authorization_id: Optional[str] = None
84
- timestamp: int
85
- nonce: str
86
- schema_version: int = Field(default=SCHEMA_VERSION)
87
-
88
-
89
- class CanonicalRevokePayload(BaseModel, CanonicalPayloadMixin):
90
- """
91
- STRICTLY contains ONLY revocation fields.
92
- """
93
- principal_id: str
94
- authorization_id: str
95
- timestamp: int
96
- nonce: str
97
- schema_version: int = Field(default=SCHEMA_VERSION)
98
-
99
-
100
- class CanonicalRegistrationPayload(BaseModel, CanonicalPayloadMixin):
101
- """
102
- STRICTLY contains ONLY registration fields.
103
- """
104
- principal_id: str
105
- budget_microalgos: Optional[StrictInt] = None
106
- timestamp: int
107
- nonce: str
108
- schema_version: int = Field(default=SCHEMA_VERSION)
1
+ from typing import Optional
2
+ from pydantic import BaseModel, Field, StrictInt
3
+ from .canonical import canonicalize_payload
4
+
5
+ SCHEMA_VERSION = 1
6
+
7
+ class CanonicalPayloadMixin:
8
+ """
9
+ PROTOCOL LAYER: Single Serialization Authority.
10
+
11
+ ALL serialization MUST derive from _serialize().
12
+ Direct model_dump() calls are FORBIDDEN in protocol code.
13
+
14
+ Invariant enforced by construction:
15
+ wire_payload == signed_payload == verified_payload
16
+ """
17
+
18
+ def _serialize(self) -> dict:
19
+ """SINGLE INTERNAL SERIALIZATION SOURCE.
20
+ Every external serialization method MUST derive from this.
21
+ Uses exclude_unset=True to prevent default injection drift."""
22
+ return self.model_dump(exclude_unset=True)
23
+
24
+ def to_canonical_bytes(self) -> bytes:
25
+ """Returns deterministic canonical bytes for signing/verification."""
26
+ return canonicalize_payload(self._serialize())
27
+
28
+ def to_wire_dict(self) -> dict:
29
+ """Returns the EXACT dict for HTTP transport.
30
+ Guaranteed identical field set to what is signed/verified."""
31
+ return self._serialize()
32
+
33
+ # Backward compatibility alias (used by signing.py, consent.py, payment.py)
34
+ get_canonical_bytes = to_canonical_bytes
35
+
36
+ class CanonicalPaymentPayload(BaseModel, CanonicalPayloadMixin):
37
+ """
38
+ SINGLE SOURCE OF TRUTH (Phase 2).
39
+ Represents the exact fields that are signed and verified for Payment.
40
+ """
41
+ # Core Identity
42
+ principal_id: str
43
+ resource_url: str
44
+ purpose: str
45
+ microalgos: StrictInt = Field(gt=0)
46
+
47
+ # Target
48
+ receiver_address: Optional[str] = None
49
+ receiver: Optional[str] = None
50
+
51
+ # Cryptographic Nonces
52
+ nonce: str
53
+ timestamp: int
54
+
55
+ # Authorization Context
56
+ authorization_id: Optional[str] = None
57
+ consent_hash: str
58
+
59
+ # Correlation & Idempotency
60
+ idempotency_key: str
61
+ correlation_id: str
62
+ session_id: Optional[str] = None
63
+
64
+ # Fiduciary Metadata (Phase A1)
65
+ fiduciary_name: Optional[str] = None
66
+ fiduciary_version: Optional[str] = None
67
+ hash_version: Optional[int] = 1
68
+
69
+ # Versioning (Phase 5)
70
+ schema_version: int = Field(default=SCHEMA_VERSION)
71
+
72
+
73
+ class CanonicalAuthorizationPayload(BaseModel, CanonicalPayloadMixin):
74
+ """
75
+ STRICTLY contains ONLY authorization lifecycle fields.
76
+ """
77
+ principal_id: str
78
+ logic_sig_b64: str
79
+ max_amount: StrictInt = Field(gt=0)
80
+ expires_at_unix: StrictInt
81
+ receiver: Optional[str] = None
82
+ purpose_hash: Optional[str] = None
83
+ authorization_id: Optional[str] = None
84
+ timestamp: int
85
+ nonce: str
86
+ schema_version: int = Field(default=SCHEMA_VERSION)
87
+
88
+
89
+ class CanonicalRevokePayload(BaseModel, CanonicalPayloadMixin):
90
+ """
91
+ STRICTLY contains ONLY revocation fields.
92
+ """
93
+ principal_id: str
94
+ authorization_id: str
95
+ timestamp: int
96
+ nonce: str
97
+ schema_version: int = Field(default=SCHEMA_VERSION)
98
+
99
+
100
+ class CanonicalRegistrationPayload(BaseModel, CanonicalPayloadMixin):
101
+ """
102
+ STRICTLY contains ONLY registration fields.
103
+ """
104
+ principal_id: str
105
+ budget_microalgos: Optional[StrictInt] = None
106
+ timestamp: int
107
+ nonce: str
108
+ schema_version: int = Field(default=SCHEMA_VERSION)
@@ -1,76 +1,76 @@
1
- import base64
2
- import nacl.signing
3
- import nacl.encoding
4
- from typing import Any
5
- from .models import CanonicalPaymentPayload
6
-
7
- def sign_payload(payload: Any, private_key_b64: str) -> str:
8
- """
9
- Signs a CanonicalPaymentPayload (Phase 4).
10
- Expects private_key_b64 (32-byte seed in b64).
11
- """
12
- seed = base64.b64decode(private_key_b64)
13
- if len(seed) > 32:
14
- seed = seed[:32]
15
-
16
- signing_key = nacl.signing.SigningKey(seed)
17
- canonical_bytes = payload.get_canonical_bytes()
18
-
19
- signature = signing_key.sign(canonical_bytes)
20
- return base64.b64encode(signature.signature).decode("utf-8")
21
-
22
- def verify_signature(payload: Any, signature_b64: str, wallet_address: str) -> bool:
23
- """
24
- Verifies a CanonicalPaymentPayload signature (Phase 4).
25
- wallet_address is used as the public key.
26
- """
27
- from algosdk import encoding
28
- import nacl.exceptions
29
-
30
- # 1. Get canonical bytes
31
- canonical_bytes = payload.get_canonical_bytes()
32
-
33
- # 2. Decode signature and key
34
- sig_bytes = base64.b64decode(signature_b64)
35
- pub_key_bytes = encoding.decode_address(wallet_address)
36
- verify_key = nacl.signing.VerifyKey(pub_key_bytes)
37
-
38
- # 3. Verify
39
- try:
40
- verify_key.verify(canonical_bytes, sig_bytes)
41
- return True
42
- except nacl.exceptions.BadSignatureError:
43
- return False
44
-
45
-
46
- def verify_raw_signature(raw_payload_dict: dict, signature_b64: str, wallet_address: str) -> bool:
47
- """
48
- PRODUCTION-GRADE: Verifies signature against raw transport payload.
49
-
50
- NO model reconstruction. Canonicalizes the raw wire dict directly.
51
- This eliminates Pydantic hydration drift, default injection, and
52
- triple-reconstruction attacks.
53
-
54
- Flow: raw_wire_dict → canonicalize → verify
55
-
56
- This is how Stripe, AWS SigV4, and WebAuthn verify signatures:
57
- against exact transport bytes, never reconstructed model state.
58
- """
59
- from .canonical import canonicalize_payload
60
- from algosdk import encoding
61
- import nacl.exceptions
62
-
63
- # 1. Canonicalize raw transport payload directly
64
- canonical_bytes = canonicalize_payload(raw_payload_dict)
65
-
66
- # 2. Decode signature and public key
67
- sig_bytes = base64.b64decode(signature_b64)
68
- pub_key_bytes = encoding.decode_address(wallet_address)
69
- verify_key = nacl.signing.VerifyKey(pub_key_bytes)
70
-
71
- # 3. Verify
72
- try:
73
- verify_key.verify(canonical_bytes, sig_bytes)
74
- return True
75
- except nacl.exceptions.BadSignatureError:
76
- return False
1
+ import base64
2
+ import nacl.signing
3
+ import nacl.encoding
4
+ from typing import Any
5
+ from .models import CanonicalPaymentPayload
6
+
7
+ def sign_payload(payload: Any, private_key_b64: str) -> str:
8
+ """
9
+ Signs a CanonicalPaymentPayload (Phase 4).
10
+ Expects private_key_b64 (32-byte seed in b64).
11
+ """
12
+ seed = base64.b64decode(private_key_b64)
13
+ if len(seed) > 32:
14
+ seed = seed[:32]
15
+
16
+ signing_key = nacl.signing.SigningKey(seed)
17
+ canonical_bytes = payload.get_canonical_bytes()
18
+
19
+ signature = signing_key.sign(canonical_bytes)
20
+ return base64.b64encode(signature.signature).decode("utf-8")
21
+
22
+ def verify_signature(payload: Any, signature_b64: str, wallet_address: str) -> bool:
23
+ """
24
+ Verifies a CanonicalPaymentPayload signature (Phase 4).
25
+ wallet_address is used as the public key.
26
+ """
27
+ from algosdk import encoding
28
+ import nacl.exceptions
29
+
30
+ # 1. Get canonical bytes
31
+ canonical_bytes = payload.get_canonical_bytes()
32
+
33
+ # 2. Decode signature and key
34
+ sig_bytes = base64.b64decode(signature_b64)
35
+ pub_key_bytes = encoding.decode_address(wallet_address)
36
+ verify_key = nacl.signing.VerifyKey(pub_key_bytes)
37
+
38
+ # 3. Verify
39
+ try:
40
+ verify_key.verify(canonical_bytes, sig_bytes)
41
+ return True
42
+ except nacl.exceptions.BadSignatureError:
43
+ return False
44
+
45
+
46
+ def verify_raw_signature(raw_payload_dict: dict, signature_b64: str, wallet_address: str) -> bool:
47
+ """
48
+ PRODUCTION-GRADE: Verifies signature against raw transport payload.
49
+
50
+ NO model reconstruction. Canonicalizes the raw wire dict directly.
51
+ This eliminates Pydantic hydration drift, default injection, and
52
+ triple-reconstruction attacks.
53
+
54
+ Flow: raw_wire_dict → canonicalize → verify
55
+
56
+ This is how Stripe, AWS SigV4, and WebAuthn verify signatures:
57
+ against exact transport bytes, never reconstructed model state.
58
+ """
59
+ from .canonical import canonicalize_payload
60
+ from algosdk import encoding
61
+ import nacl.exceptions
62
+
63
+ # 1. Canonicalize raw transport payload directly
64
+ canonical_bytes = canonicalize_payload(raw_payload_dict)
65
+
66
+ # 2. Decode signature and public key
67
+ sig_bytes = base64.b64decode(signature_b64)
68
+ pub_key_bytes = encoding.decode_address(wallet_address)
69
+ verify_key = nacl.signing.VerifyKey(pub_key_bytes)
70
+
71
+ # 3. Verify
72
+ try:
73
+ verify_key.verify(canonical_bytes, sig_bytes)
74
+ return True
75
+ except nacl.exceptions.BadSignatureError:
76
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentguard-python-sdk
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A production-grade middleware for AI agents to perform on-chain payments and verifiable consent.
5
5
  Author: AgentGuard Team
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentguard-python-sdk"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "A production-grade middleware for AI agents to perform on-chain payments and verifiable consent."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"