atp-protocol 1.2.0__py3-none-any.whl → 1.3.0__py3-none-any.whl
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.
- atp/__init__.py +9 -1
- atp/client.py +618 -0
- atp/config.py +5 -0
- atp/encryption.py +155 -0
- atp/middleware.py +442 -103
- atp/schemas.py +186 -0
- atp/settlement_client.py +608 -52
- atp_protocol-1.3.0.dist-info/METADATA +590 -0
- atp_protocol-1.3.0.dist-info/RECORD +11 -0
- atp_protocol-1.2.0.dist-info/METADATA +0 -401
- atp_protocol-1.2.0.dist-info/RECORD +0 -9
- {atp_protocol-1.2.0.dist-info → atp_protocol-1.3.0.dist-info}/LICENSE +0 -0
- {atp_protocol-1.2.0.dist-info → atp_protocol-1.3.0.dist-info}/WHEEL +0 -0
atp/encryption.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Encryption utilities for ATP Protocol.
|
|
3
|
+
|
|
4
|
+
This module provides encryption/decryption functionality to protect agent
|
|
5
|
+
responses until payment is verified.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import base64
|
|
11
|
+
import os
|
|
12
|
+
from typing import Any, Dict, Optional
|
|
13
|
+
|
|
14
|
+
from cryptography.fernet import Fernet
|
|
15
|
+
from loguru import logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ResponseEncryptor:
|
|
19
|
+
"""
|
|
20
|
+
Encrypts and decrypts agent responses using Fernet symmetric encryption.
|
|
21
|
+
|
|
22
|
+
The encryption key is derived from a secret that should be kept secure.
|
|
23
|
+
In production, this should be set via environment variable.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, encryption_key: Optional[str] = None):
|
|
27
|
+
"""
|
|
28
|
+
Initialize the encryptor.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
encryption_key: Base64-encoded Fernet key. If not provided, generates
|
|
32
|
+
a new key or uses ATP_ENCRYPTION_KEY from environment.
|
|
33
|
+
"""
|
|
34
|
+
if encryption_key:
|
|
35
|
+
self.key = encryption_key.encode()
|
|
36
|
+
else:
|
|
37
|
+
# Try to get from environment, or generate a new one
|
|
38
|
+
env_key = os.getenv("ATP_ENCRYPTION_KEY")
|
|
39
|
+
if env_key:
|
|
40
|
+
self.key = env_key.encode()
|
|
41
|
+
else:
|
|
42
|
+
# Generate a new key (for development/testing)
|
|
43
|
+
# In production, this should be set via environment variable
|
|
44
|
+
logger.warning(
|
|
45
|
+
"No ATP_ENCRYPTION_KEY found in environment. "
|
|
46
|
+
"Generating a new key. This key will not persist across restarts."
|
|
47
|
+
)
|
|
48
|
+
self.key = Fernet.generate_key()
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
self.fernet = Fernet(self.key)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"Invalid encryption key format: {e}. "
|
|
55
|
+
"Key must be a valid base64-encoded Fernet key."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def encrypt(self, data: str) -> str:
|
|
59
|
+
"""
|
|
60
|
+
Encrypt a string.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
data: String to encrypt.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Base64-encoded encrypted string.
|
|
67
|
+
"""
|
|
68
|
+
encrypted = self.fernet.encrypt(data.encode())
|
|
69
|
+
return base64.b64encode(encrypted).decode()
|
|
70
|
+
|
|
71
|
+
def decrypt(self, encrypted_data: str) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Decrypt a string.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
encrypted_data: Base64-encoded encrypted string.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Decrypted string.
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
encrypted_bytes = base64.b64decode(encrypted_data.encode())
|
|
83
|
+
decrypted = self.fernet.decrypt(encrypted_bytes)
|
|
84
|
+
return decrypted.decode()
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.error(f"Decryption failed: {e}")
|
|
87
|
+
raise ValueError(f"Failed to decrypt data: {e}")
|
|
88
|
+
|
|
89
|
+
def encrypt_response_data(
|
|
90
|
+
self, response_data: Dict[str, Any], fields_to_encrypt: list[str] = None
|
|
91
|
+
) -> Dict[str, Any]:
|
|
92
|
+
"""
|
|
93
|
+
Encrypt specific fields in a response dictionary.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
response_data: Response dictionary containing agent output.
|
|
97
|
+
fields_to_encrypt: List of field names to encrypt. Defaults to
|
|
98
|
+
common output fields: ["output", "response", "result", "message"].
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Response dictionary with specified fields encrypted.
|
|
102
|
+
"""
|
|
103
|
+
if fields_to_encrypt is None:
|
|
104
|
+
fields_to_encrypt = ["output", "response", "result", "message"]
|
|
105
|
+
|
|
106
|
+
encrypted_data = response_data.copy()
|
|
107
|
+
|
|
108
|
+
for field in fields_to_encrypt:
|
|
109
|
+
if field in encrypted_data and isinstance(
|
|
110
|
+
encrypted_data[field], str
|
|
111
|
+
):
|
|
112
|
+
encrypted_data[field] = self.encrypt(encrypted_data[field])
|
|
113
|
+
# Mark as encrypted
|
|
114
|
+
encrypted_data[f"{field}_encrypted"] = True
|
|
115
|
+
|
|
116
|
+
return encrypted_data
|
|
117
|
+
|
|
118
|
+
def decrypt_response_data(
|
|
119
|
+
self, response_data: Dict[str, Any], fields_to_decrypt: list[str] = None
|
|
120
|
+
) -> Dict[str, Any]:
|
|
121
|
+
"""
|
|
122
|
+
Decrypt specific fields in a response dictionary.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
response_data: Response dictionary with encrypted fields.
|
|
126
|
+
fields_to_decrypt: List of field names to decrypt. Defaults to
|
|
127
|
+
common output fields: ["output", "response", "result", "message"].
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Response dictionary with specified fields decrypted.
|
|
131
|
+
"""
|
|
132
|
+
if fields_to_decrypt is None:
|
|
133
|
+
fields_to_decrypt = ["output", "response", "result", "message"]
|
|
134
|
+
|
|
135
|
+
decrypted_data = response_data.copy()
|
|
136
|
+
|
|
137
|
+
for field in fields_to_decrypt:
|
|
138
|
+
if (
|
|
139
|
+
field in decrypted_data
|
|
140
|
+
and isinstance(decrypted_data[field], str)
|
|
141
|
+
and decrypted_data.get(f"{field}_encrypted", False)
|
|
142
|
+
):
|
|
143
|
+
try:
|
|
144
|
+
decrypted_data[field] = self.decrypt(decrypted_data[field])
|
|
145
|
+
# Remove encryption marker
|
|
146
|
+
decrypted_data.pop(f"{field}_encrypted", None)
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error(
|
|
149
|
+
f"Failed to decrypt field '{field}': {e}"
|
|
150
|
+
)
|
|
151
|
+
# Keep encrypted if decryption fails
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
return decrypted_data
|
|
155
|
+
|