connectonion 0.5.8__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.
- connectonion/__init__.py +78 -0
- connectonion/address.py +320 -0
- connectonion/agent.py +450 -0
- connectonion/announce.py +84 -0
- connectonion/asgi.py +287 -0
- connectonion/auto_debug_exception.py +181 -0
- connectonion/cli/__init__.py +3 -0
- connectonion/cli/browser_agent/__init__.py +5 -0
- connectonion/cli/browser_agent/browser.py +243 -0
- connectonion/cli/browser_agent/prompt.md +107 -0
- connectonion/cli/commands/__init__.py +1 -0
- connectonion/cli/commands/auth_commands.py +527 -0
- connectonion/cli/commands/browser_commands.py +27 -0
- connectonion/cli/commands/create.py +511 -0
- connectonion/cli/commands/deploy_commands.py +220 -0
- connectonion/cli/commands/doctor_commands.py +173 -0
- connectonion/cli/commands/init.py +469 -0
- connectonion/cli/commands/project_cmd_lib.py +828 -0
- connectonion/cli/commands/reset_commands.py +149 -0
- connectonion/cli/commands/status_commands.py +168 -0
- connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +2010 -0
- connectonion/cli/docs/connectonion.md +1256 -0
- connectonion/cli/docs.md +123 -0
- connectonion/cli/main.py +148 -0
- connectonion/cli/templates/meta-agent/README.md +287 -0
- connectonion/cli/templates/meta-agent/agent.py +196 -0
- connectonion/cli/templates/meta-agent/prompts/answer_prompt.md +9 -0
- connectonion/cli/templates/meta-agent/prompts/docs_retrieve_prompt.md +15 -0
- connectonion/cli/templates/meta-agent/prompts/metagent.md +71 -0
- connectonion/cli/templates/meta-agent/prompts/think_prompt.md +18 -0
- connectonion/cli/templates/minimal/README.md +56 -0
- connectonion/cli/templates/minimal/agent.py +40 -0
- connectonion/cli/templates/playwright/README.md +118 -0
- connectonion/cli/templates/playwright/agent.py +336 -0
- connectonion/cli/templates/playwright/prompt.md +102 -0
- connectonion/cli/templates/playwright/requirements.txt +3 -0
- connectonion/cli/templates/web-research/agent.py +122 -0
- connectonion/connect.py +128 -0
- connectonion/console.py +539 -0
- connectonion/debug_agent/__init__.py +13 -0
- connectonion/debug_agent/agent.py +45 -0
- connectonion/debug_agent/prompts/debug_assistant.md +72 -0
- connectonion/debug_agent/runtime_inspector.py +406 -0
- connectonion/debug_explainer/__init__.py +10 -0
- connectonion/debug_explainer/explain_agent.py +114 -0
- connectonion/debug_explainer/explain_context.py +263 -0
- connectonion/debug_explainer/explainer_prompt.md +29 -0
- connectonion/debug_explainer/root_cause_analysis_prompt.md +43 -0
- connectonion/debugger_ui.py +1039 -0
- connectonion/decorators.py +208 -0
- connectonion/events.py +248 -0
- connectonion/execution_analyzer/__init__.py +9 -0
- connectonion/execution_analyzer/execution_analysis.py +93 -0
- connectonion/execution_analyzer/execution_analysis_prompt.md +47 -0
- connectonion/host.py +579 -0
- connectonion/interactive_debugger.py +342 -0
- connectonion/llm.py +801 -0
- connectonion/llm_do.py +307 -0
- connectonion/logger.py +300 -0
- connectonion/prompt_files/__init__.py +1 -0
- connectonion/prompt_files/analyze_contact.md +62 -0
- connectonion/prompt_files/eval_expected.md +12 -0
- connectonion/prompt_files/react_evaluate.md +11 -0
- connectonion/prompt_files/react_plan.md +16 -0
- connectonion/prompt_files/reflect.md +22 -0
- connectonion/prompts.py +144 -0
- connectonion/relay.py +200 -0
- connectonion/static/docs.html +688 -0
- connectonion/tool_executor.py +279 -0
- connectonion/tool_factory.py +186 -0
- connectonion/tool_registry.py +105 -0
- connectonion/trust.py +166 -0
- connectonion/trust_agents.py +71 -0
- connectonion/trust_functions.py +88 -0
- connectonion/tui/__init__.py +57 -0
- connectonion/tui/divider.py +39 -0
- connectonion/tui/dropdown.py +251 -0
- connectonion/tui/footer.py +31 -0
- connectonion/tui/fuzzy.py +56 -0
- connectonion/tui/input.py +278 -0
- connectonion/tui/keys.py +35 -0
- connectonion/tui/pick.py +130 -0
- connectonion/tui/providers.py +155 -0
- connectonion/tui/status_bar.py +163 -0
- connectonion/usage.py +161 -0
- connectonion/useful_events_handlers/__init__.py +16 -0
- connectonion/useful_events_handlers/reflect.py +116 -0
- connectonion/useful_plugins/__init__.py +20 -0
- connectonion/useful_plugins/calendar_plugin.py +163 -0
- connectonion/useful_plugins/eval.py +139 -0
- connectonion/useful_plugins/gmail_plugin.py +162 -0
- connectonion/useful_plugins/image_result_formatter.py +127 -0
- connectonion/useful_plugins/re_act.py +78 -0
- connectonion/useful_plugins/shell_approval.py +159 -0
- connectonion/useful_tools/__init__.py +44 -0
- connectonion/useful_tools/diff_writer.py +192 -0
- connectonion/useful_tools/get_emails.py +183 -0
- connectonion/useful_tools/gmail.py +1596 -0
- connectonion/useful_tools/google_calendar.py +613 -0
- connectonion/useful_tools/memory.py +380 -0
- connectonion/useful_tools/microsoft_calendar.py +604 -0
- connectonion/useful_tools/outlook.py +488 -0
- connectonion/useful_tools/send_email.py +205 -0
- connectonion/useful_tools/shell.py +97 -0
- connectonion/useful_tools/slash_command.py +201 -0
- connectonion/useful_tools/terminal.py +285 -0
- connectonion/useful_tools/todo_list.py +241 -0
- connectonion/useful_tools/web_fetch.py +216 -0
- connectonion/xray.py +467 -0
- connectonion-0.5.8.dist-info/METADATA +741 -0
- connectonion-0.5.8.dist-info/RECORD +113 -0
- connectonion-0.5.8.dist-info/WHEEL +4 -0
- connectonion-0.5.8.dist-info/entry_points.txt +3 -0
connectonion/__init__.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""ConnectOnion - A simple agent framework with behavior tracking."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.5.8"
|
|
4
|
+
|
|
5
|
+
# Auto-load .env files for the entire framework
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
from pathlib import Path as _Path
|
|
8
|
+
|
|
9
|
+
# Load from current working directory (where user runs their script)
|
|
10
|
+
# NOT from the module's location (framework directory)
|
|
11
|
+
load_dotenv(_Path.cwd() / ".env")
|
|
12
|
+
|
|
13
|
+
from .agent import Agent
|
|
14
|
+
from .tool_factory import create_tool_from_function
|
|
15
|
+
from .llm import LLM
|
|
16
|
+
from .logger import Logger
|
|
17
|
+
from .llm_do import llm_do
|
|
18
|
+
from .prompts import load_system_prompt
|
|
19
|
+
from .xray import xray
|
|
20
|
+
from .decorators import replay, xray_replay
|
|
21
|
+
from .useful_tools import send_email, get_emails, mark_read, mark_unread, Memory, Gmail, GoogleCalendar, Outlook, MicrosoftCalendar, WebFetch, Shell, DiffWriter, pick, yes_no, autocomplete, TodoList, SlashCommand
|
|
22
|
+
from .auto_debug_exception import auto_debug_exception
|
|
23
|
+
from .connect import connect, RemoteAgent
|
|
24
|
+
from .host import host, create_app
|
|
25
|
+
from .events import (
|
|
26
|
+
after_user_input,
|
|
27
|
+
before_llm,
|
|
28
|
+
after_llm,
|
|
29
|
+
before_each_tool,
|
|
30
|
+
before_tools,
|
|
31
|
+
after_each_tool,
|
|
32
|
+
after_tools,
|
|
33
|
+
on_error,
|
|
34
|
+
on_complete
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"Agent",
|
|
39
|
+
"LLM",
|
|
40
|
+
"Logger",
|
|
41
|
+
"create_tool_from_function",
|
|
42
|
+
"llm_do",
|
|
43
|
+
"load_system_prompt",
|
|
44
|
+
"xray",
|
|
45
|
+
"replay",
|
|
46
|
+
"xray_replay",
|
|
47
|
+
"send_email",
|
|
48
|
+
"get_emails",
|
|
49
|
+
"mark_read",
|
|
50
|
+
"mark_unread",
|
|
51
|
+
"Memory",
|
|
52
|
+
"Gmail",
|
|
53
|
+
"GoogleCalendar",
|
|
54
|
+
"Outlook",
|
|
55
|
+
"MicrosoftCalendar",
|
|
56
|
+
"WebFetch",
|
|
57
|
+
"Shell",
|
|
58
|
+
"DiffWriter",
|
|
59
|
+
"pick",
|
|
60
|
+
"yes_no",
|
|
61
|
+
"autocomplete",
|
|
62
|
+
"TodoList",
|
|
63
|
+
"SlashCommand",
|
|
64
|
+
"auto_debug_exception",
|
|
65
|
+
"connect",
|
|
66
|
+
"RemoteAgent",
|
|
67
|
+
"host",
|
|
68
|
+
"create_app",
|
|
69
|
+
"after_user_input",
|
|
70
|
+
"before_llm",
|
|
71
|
+
"after_llm",
|
|
72
|
+
"before_each_tool",
|
|
73
|
+
"before_tools",
|
|
74
|
+
"after_each_tool",
|
|
75
|
+
"after_tools",
|
|
76
|
+
"on_error",
|
|
77
|
+
"on_complete"
|
|
78
|
+
]
|
connectonion/address.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Generate and manage Ed25519 cryptographic agent identities with seed phrase recovery
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [os, pathlib, typing, nacl.signing, mnemonic] | imported by [cli/commands/auth_commands.py] | tested by [tests/test_address.py]
|
|
5
|
+
Data flow: generate() → creates 12-word seed phrase via Mnemonic → derives SigningKey from seed → creates address (0x + hex public key) → returns {address, short_address, email, seed_phrase, signing_key} | recover(seed_phrase) → validates phrase → recreates SigningKey → recreates address
|
|
6
|
+
State/Effects: save() writes to .co/keys/ directory: agent.key (binary signing key), recovery.txt (seed phrase), DO_NOT_SHARE (warning) | sets file permissions to 0o600 | load() reads from .co/keys/ and .co/config.toml | no global state
|
|
7
|
+
Integration: exposes generate(), recover(seed_phrase), save(address_data, co_dir), load(co_dir), verify(address, message, signature), sign(address_data, message) | address format: 0x + 64 hex chars (32 bytes public key) | email format: first 10 chars + @mail.openonion.ai
|
|
8
|
+
Performance: Ed25519 signing is fast (sub-millisecond) | mnemonic generation and validation are fast | file I/O minimal (only on save/load)
|
|
9
|
+
Errors: raises ImportError if pynacl or mnemonic not installed | raises ValueError for invalid recovery phrase | returns None for missing keys (graceful) | verify() returns False for invalid signatures
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Dict, Any, Optional
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from nacl.signing import SigningKey, VerifyKey
|
|
19
|
+
from mnemonic import Mnemonic
|
|
20
|
+
except ImportError:
|
|
21
|
+
# Graceful fallback if dependencies not installed
|
|
22
|
+
SigningKey = None
|
|
23
|
+
VerifyKey = None
|
|
24
|
+
Mnemonic = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def generate() -> Dict[str, Any]:
|
|
28
|
+
"""
|
|
29
|
+
Generate a new agent address with Ed25519 keys.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Dictionary containing:
|
|
33
|
+
- address: Hex-encoded public key with 0x prefix (66 chars)
|
|
34
|
+
- short_address: Truncated display format (0x3d40...660c)
|
|
35
|
+
- email: Agent's email address (0x3d4017c3@mail.openonion.ai)
|
|
36
|
+
- seed_phrase: 12-word recovery phrase
|
|
37
|
+
- signing_key: Ed25519 signing key for signatures
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
>>> addr = generate()
|
|
41
|
+
>>> print(addr['address'])
|
|
42
|
+
0x3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c
|
|
43
|
+
>>> print(addr['email'])
|
|
44
|
+
0x3d4017c3@mail.openonion.ai
|
|
45
|
+
"""
|
|
46
|
+
if SigningKey is None or Mnemonic is None:
|
|
47
|
+
raise ImportError(
|
|
48
|
+
"Required libraries not installed. "
|
|
49
|
+
"Please run: pip install pynacl mnemonic"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Generate 12-word recovery phrase
|
|
53
|
+
mnemo = Mnemonic("english")
|
|
54
|
+
seed_phrase = mnemo.generate(strength=128) # 128 bits = 12 words
|
|
55
|
+
|
|
56
|
+
# Derive seed from phrase
|
|
57
|
+
seed = mnemo.to_seed(seed_phrase)
|
|
58
|
+
|
|
59
|
+
# Create Ed25519 signing key from first 32 bytes
|
|
60
|
+
signing_key = SigningKey(seed[:32])
|
|
61
|
+
|
|
62
|
+
# Create address (hex-encoded public key with 0x prefix)
|
|
63
|
+
public_key_bytes = bytes(signing_key.verify_key)
|
|
64
|
+
address = "0x" + public_key_bytes.hex()
|
|
65
|
+
|
|
66
|
+
# Create short display format
|
|
67
|
+
short_address = f"{address[:6]}...{address[-4:]}"
|
|
68
|
+
|
|
69
|
+
# Create email address (first 10 chars of address)
|
|
70
|
+
email = f"{address[:10]}@mail.openonion.ai"
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
"address": address,
|
|
74
|
+
"short_address": short_address,
|
|
75
|
+
"email": email,
|
|
76
|
+
"email_active": False, # Email inactive until authenticated
|
|
77
|
+
"seed_phrase": seed_phrase,
|
|
78
|
+
"signing_key": signing_key
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def recover(seed_phrase: str) -> Dict[str, Any]:
|
|
83
|
+
"""
|
|
84
|
+
Recover agent address from a recovery phrase.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
seed_phrase: 12-word recovery phrase
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Dictionary containing address and signing_key
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If recovery phrase is invalid
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
>>> addr = recover("canyon robot vacuum circle...")
|
|
97
|
+
>>> print(addr['address'])
|
|
98
|
+
0x3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c
|
|
99
|
+
"""
|
|
100
|
+
if Mnemonic is None or SigningKey is None:
|
|
101
|
+
raise ImportError(
|
|
102
|
+
"Required libraries not installed. "
|
|
103
|
+
"Please run: pip install pynacl mnemonic"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
mnemo = Mnemonic("english")
|
|
107
|
+
|
|
108
|
+
# Validate recovery phrase
|
|
109
|
+
if not mnemo.check(seed_phrase):
|
|
110
|
+
raise ValueError("Invalid recovery phrase")
|
|
111
|
+
|
|
112
|
+
# Derive seed from phrase
|
|
113
|
+
seed = mnemo.to_seed(seed_phrase)
|
|
114
|
+
|
|
115
|
+
# Recreate signing key
|
|
116
|
+
signing_key = SigningKey(seed[:32])
|
|
117
|
+
|
|
118
|
+
# Recreate address
|
|
119
|
+
public_key_bytes = bytes(signing_key.verify_key)
|
|
120
|
+
address = "0x" + public_key_bytes.hex()
|
|
121
|
+
short_address = f"{address[:6]}...{address[-4:]}"
|
|
122
|
+
|
|
123
|
+
# Create email address (first 10 chars of address)
|
|
124
|
+
email = f"{address[:10]}@mail.openonion.ai"
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
"address": address,
|
|
128
|
+
"short_address": short_address,
|
|
129
|
+
"email": email,
|
|
130
|
+
"email_active": False, # Email inactive until authenticated
|
|
131
|
+
"signing_key": signing_key
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def save(address_data: Dict[str, Any], co_dir: Path) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Save agent keys to .co/keys/ directory.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
address_data: Dictionary from generate() or recover()
|
|
141
|
+
co_dir: Path to .co directory
|
|
142
|
+
|
|
143
|
+
Creates:
|
|
144
|
+
- .co/keys/agent.key (private signing key)
|
|
145
|
+
- .co/keys/recovery.txt (12-word phrase)
|
|
146
|
+
- .co/keys/DO_NOT_SHARE (warning file)
|
|
147
|
+
"""
|
|
148
|
+
# Create keys directory
|
|
149
|
+
keys_dir = co_dir / "keys"
|
|
150
|
+
keys_dir.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
|
|
152
|
+
# Save private key (binary format)
|
|
153
|
+
key_file = keys_dir / "agent.key"
|
|
154
|
+
key_file.write_bytes(bytes(address_data["signing_key"]))
|
|
155
|
+
if sys.platform != 'win32':
|
|
156
|
+
key_file.chmod(0o600) # Read/write for owner only (Unix/Mac only)
|
|
157
|
+
|
|
158
|
+
# Save recovery phrase if present
|
|
159
|
+
if "seed_phrase" in address_data:
|
|
160
|
+
recovery_file = keys_dir / "recovery.txt"
|
|
161
|
+
recovery_file.write_text(address_data["seed_phrase"], encoding='utf-8')
|
|
162
|
+
if sys.platform != 'win32':
|
|
163
|
+
recovery_file.chmod(0o600) # Read/write for owner only (Unix/Mac only)
|
|
164
|
+
|
|
165
|
+
# Create warning file
|
|
166
|
+
warning_file = keys_dir / "DO_NOT_SHARE"
|
|
167
|
+
if not warning_file.exists():
|
|
168
|
+
warning_content = """⚠️ WARNING: PRIVATE KEYS - DO NOT SHARE ⚠️
|
|
169
|
+
|
|
170
|
+
This directory contains private cryptographic keys.
|
|
171
|
+
NEVER share these files or commit them to version control.
|
|
172
|
+
Anyone with these keys can impersonate your agent.
|
|
173
|
+
|
|
174
|
+
Files:
|
|
175
|
+
- agent.key: Your agent's private signing key
|
|
176
|
+
- recovery.txt: 12-word recovery phrase
|
|
177
|
+
|
|
178
|
+
Keep these files secure and backed up.
|
|
179
|
+
"""
|
|
180
|
+
warning_file.write_text(warning_content, encoding='utf-8')
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def load(co_dir: Path) -> Optional[Dict[str, Any]]:
|
|
184
|
+
"""
|
|
185
|
+
Load existing agent keys from .co/keys/ directory.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
co_dir: Path to .co directory
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Dictionary with address and signing_key, or None if not found
|
|
192
|
+
|
|
193
|
+
Example:
|
|
194
|
+
>>> addr = load(Path(".co"))
|
|
195
|
+
>>> if addr:
|
|
196
|
+
... print(addr['address'])
|
|
197
|
+
"""
|
|
198
|
+
if SigningKey is None:
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
keys_dir = co_dir / "keys"
|
|
202
|
+
key_file = keys_dir / "agent.key"
|
|
203
|
+
|
|
204
|
+
if not key_file.exists():
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
# Load signing key
|
|
209
|
+
key_bytes = key_file.read_bytes()
|
|
210
|
+
signing_key = SigningKey(key_bytes)
|
|
211
|
+
|
|
212
|
+
# Derive address from public key
|
|
213
|
+
public_key_bytes = bytes(signing_key.verify_key)
|
|
214
|
+
address = "0x" + public_key_bytes.hex()
|
|
215
|
+
short_address = f"{address[:6]}...{address[-4:]}"
|
|
216
|
+
|
|
217
|
+
# Try to load recovery phrase if it exists
|
|
218
|
+
recovery_file = keys_dir / "recovery.txt"
|
|
219
|
+
seed_phrase = None
|
|
220
|
+
if recovery_file.exists():
|
|
221
|
+
seed_phrase = recovery_file.read_text(encoding='utf-8').strip()
|
|
222
|
+
|
|
223
|
+
# Try to load email and activation status from config.toml
|
|
224
|
+
email = f"{address[:10]}@mail.openonion.ai" # Default
|
|
225
|
+
email_active = False # Default to inactive
|
|
226
|
+
|
|
227
|
+
config_path = co_dir / "config.toml"
|
|
228
|
+
if config_path.exists():
|
|
229
|
+
try:
|
|
230
|
+
import toml
|
|
231
|
+
config = toml.load(config_path)
|
|
232
|
+
if "agent" in config:
|
|
233
|
+
email = config["agent"].get("email", email)
|
|
234
|
+
email_active = config["agent"].get("email_active", False)
|
|
235
|
+
except Exception:
|
|
236
|
+
pass # Use defaults if config reading fails
|
|
237
|
+
|
|
238
|
+
result = {
|
|
239
|
+
"address": address,
|
|
240
|
+
"short_address": short_address,
|
|
241
|
+
"email": email,
|
|
242
|
+
"email_active": email_active,
|
|
243
|
+
"signing_key": signing_key
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if seed_phrase:
|
|
247
|
+
result["seed_phrase"] = seed_phrase
|
|
248
|
+
|
|
249
|
+
return result
|
|
250
|
+
|
|
251
|
+
except Exception:
|
|
252
|
+
# Invalid key file or other error
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def verify(address: str, message: bytes, signature: bytes) -> bool:
|
|
257
|
+
"""
|
|
258
|
+
Verify a signature using an agent's address.
|
|
259
|
+
|
|
260
|
+
Since the address IS the public key (hex-encoded), we can verify
|
|
261
|
+
signatures directly without needing additional information.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
address: Agent's hex address (0x...)
|
|
265
|
+
message: Message that was signed
|
|
266
|
+
signature: 64-byte Ed25519 signature
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
True if signature is valid, False otherwise
|
|
270
|
+
|
|
271
|
+
Example:
|
|
272
|
+
>>> msg = b"Hello, ConnectOnion!"
|
|
273
|
+
>>> sig = agent.sign(msg)
|
|
274
|
+
>>> verify(agent_address, msg, sig)
|
|
275
|
+
True
|
|
276
|
+
"""
|
|
277
|
+
if VerifyKey is None:
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
# Validate address format
|
|
282
|
+
if not address.startswith("0x") or len(address) != 66:
|
|
283
|
+
return False
|
|
284
|
+
|
|
285
|
+
# Extract public key from address (it IS the public key!)
|
|
286
|
+
public_key_hex = address[2:]
|
|
287
|
+
public_key_bytes = bytes.fromhex(public_key_hex)
|
|
288
|
+
|
|
289
|
+
# Create verify key
|
|
290
|
+
verify_key = VerifyKey(public_key_bytes)
|
|
291
|
+
|
|
292
|
+
# Verify signature
|
|
293
|
+
verify_key.verify(message, signature)
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
except Exception:
|
|
297
|
+
# Invalid signature, wrong key, or other error
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def sign(address_data: Dict[str, Any], message: bytes) -> bytes:
|
|
302
|
+
"""
|
|
303
|
+
Sign a message with the agent's private key.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
address_data: Dictionary from generate() or load()
|
|
307
|
+
message: Message to sign
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
64-byte Ed25519 signature
|
|
311
|
+
|
|
312
|
+
Example:
|
|
313
|
+
>>> addr = load(Path(".co"))
|
|
314
|
+
>>> sig = sign(addr, b"Hello!")
|
|
315
|
+
"""
|
|
316
|
+
if "signing_key" not in address_data:
|
|
317
|
+
raise ValueError("No signing key in address data")
|
|
318
|
+
|
|
319
|
+
signed = address_data["signing_key"].sign(message)
|
|
320
|
+
return signed.signature
|