Cryptographic identity for AI agents. Ed25519 keypairs, RFC 9421 HTTP Message Signatures, self-resolving keyids.
Project description
envoys
Cryptographic identity for AI agents. Ed25519 keypairs, RFC 9421 HTTP Message Signatures, self-resolving keyids.
Sign every HTTP request an agent makes, so the receiver can cryptographically verify which agent sent it — no shared secret, no central broker. Each agent hosts its own public key at a resolvable URL; verifiers fetch it and check the signature.
Python SDK, requires Python 3.10+. Mirrors @envoys/sdk (Node) — same surface, same wire format, byte-compatible against the envoys-rfc9421 §14 test vectors.
Install
pip install envoys
Quick start
Register an agent
import asyncio
from envoys import Envoys, RegisterOptions
async def main():
client, result = await Envoys.register(RegisterOptions(
account_key = "ak_...", # from envoys.me dashboard
name = "scout",
))
print(result.address) # scout@your-handle.envoys.me
print(result.private_key) # PEM PKCS8 — store securely, shown once
asyncio.run(main())
Sign an outgoing HTTP request
from_env() reads the agent's credentials from environment variables — populate them from register's result above, or from your envoys.me dashboard.
from envoys import Envoys
import httpx
envoys = Envoys.from_env() # ENVOYS_AGENT_KEY, ENVOYS_ADDRESS, ENVOYS_PUBLIC_KEY, ENVOYS_PRIVATE_KEY
body = {"task": "summarize", "url": "https://example.com/doc"}
headers = envoys.sign_request("POST", "/api/task", body)
with httpx.Client() as http:
r = http.post("https://other-agent.example/api/task",
headers={**headers, "Content-Type": "application/json"},
json=body)
Verify an incoming request
import asyncio
from envoys import Envoys, VerifyRequestOptions
async def verify(method, path, headers, body):
result = await Envoys.verify_request(
method, path, headers, body,
VerifyRequestOptions(allowlist=["scout@trusted.envoys.me"]),
)
if not result.verified:
return None, result.error
return result.address, None
The verifier enforces component coverage per spec §5.5: signatures must cover
@method and @path, plus content-digest whenever the request has a body —
a signature that leaves the body uncovered is rejected even if
cryptographically valid (digest-downgrade protection, since 0.2.0).
Optional: bind the signature to the target host (@authority)
headers = envoys.sign_request("POST", "/rpc", body, authority="receiver.example.com")
Covers RFC 9421 @authority so the signature is scoped to one receiving
service — relayed to any other host it fails verification. The verifier
reconstructs the value from VerifyRequestOptions(authority=...) (set this
behind a proxy that rewrites Host) or the request's Host header. Opt-in:
verifiers older than 0.2.0 reject signatures covering components they don't
reconstruct. Spec v1.6.0 §4.2.
Surface
Mirrors @envoys/sdk one-to-one with Python-idiomatic naming. Methods shown with await are coroutines and must be awaited; the rest are synchronous.
| Operation | Method |
|---|---|
| Register a new agent | await Envoys.register(opts) |
| Construct from environment | Envoys.from_env() |
| Key rotation sync | await client.sync_keys() |
| Sign arbitrary payload | client.sign(payload) |
| Sign HTTP request (RFC 9421) | client.sign_request(method, path, body, *, tag=None) |
| Sign Agent Card (JWS EdDSA) | client.sign_agent_card(card) |
| Verify HTTP request | await Envoys.verify_request(method, path, headers, body, options) |
| Verify Agent Card | await Envoys.verify_agent_card(jws) |
| Verify payload signature | Envoys.verify(payload, signature, public_key) |
| Resolve public key by address | await Envoys.resolve_public_key(address) |
| Resolve dual-shape keyid | await Envoys.resolve_key_from_keyid(keyid_url) |
| Resolve did:web | await Envoys.resolve_did_web(domain) |
| Reset pin | Envoys.reset_pin(address) |
| Clear caches (testing) | Envoys.clear_pins(), Envoys.clear_replay_cache(), Envoys.clear_key_cache() |
Spec & conformance
The wire format is published at https://envoys.me/specs/signature/v1. Every operation in this SDK byte-matches the §14 reference vectors at envoys-rfc9421, and cross-implementation interop is verified bidirectionally against @envoys/sdk (Node) — Python signs / Node verifies, and Node signs / Python verifies. The SDK also resolves W3C did:web Documents in both Ed25519VerificationKey2020 and JsonWebKey2020 shapes.
License
Apache-2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file envoys-0.2.0.tar.gz.
File metadata
- Download URL: envoys-0.2.0.tar.gz
- Upload date:
- Size: 24.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb7b4a9b302552accb0b2b8b7ee5aa8ed378fc2a397c04509db338f064b37cd2
|
|
| MD5 |
b868f717c7e8d2612dd7a4e65ca8b2c5
|
|
| BLAKE2b-256 |
a5ac2316bcfa6ba574fedbb47c532353e44071baaf626f00f75b7ca9aae40526
|
File details
Details for the file envoys-0.2.0-py3-none-any.whl.
File metadata
- Download URL: envoys-0.2.0-py3-none-any.whl
- Upload date:
- Size: 18.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0452904a407f6ae98fc7360304f4245ea4aceac8cbd12cd9c9b68c7a7a712c26
|
|
| MD5 |
c6f11cf6332c09699ed815ed3e99a306
|
|
| BLAKE2b-256 |
00162b112623704c5712f5306e0d96f42a9ffae1839b6ebb314e227920ce8733
|