Metadata-Version: 2.4
Name: envoys
Version: 0.2.0
Summary: Cryptographic identity for AI agents. Ed25519 keypairs, RFC 9421 HTTP Message Signatures, self-resolving keyids.
Project-URL: Homepage, https://envoys.me
Project-URL: Repository, https://github.com/jschoemaker/Envoys-public
Project-URL: Issues, https://github.com/jschoemaker/Envoys-public/issues
Project-URL: Specification, https://envoys.me/specs/signature/v1
Author-email: jerown <jeroenschoemaker1@gmail.com>
License: Apache-2.0
License-File: LICENSE
Keywords: a2a,agent-identity,ai-agents,cryptography,ed25519,envoys,http-signatures,rfc9421
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Requires-Dist: cryptography>=42
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Description-Content-Type: text/markdown

# envoys

[![PyPI](https://img.shields.io/pypi/v/envoys)](https://pypi.org/project/envoys/)
[![Python](https://img.shields.io/pypi/pyversions/envoys)](https://pypi.org/project/envoys/)
[![License](https://img.shields.io/pypi/l/envoys)](https://github.com/jschoemaker/Envoys-public/blob/main/LICENSE)

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`](https://www.npmjs.com/package/@envoys/sdk) (Node) — same surface, same wire format, byte-compatible against the [`envoys-rfc9421`](https://github.com/jschoemaker/Envoys-public/tree/main/fixtures/envoys-rfc9421) §14 test vectors.

## Install

```bash
pip install envoys
```

## Quick start

### Register an agent

```python
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.

```python
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

```python
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`)

```python
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`](https://github.com/jschoemaker/Envoys-public/tree/main/packages/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`](https://github.com/jschoemaker/Envoys-public/tree/main/fixtures/envoys-rfc9421), and cross-implementation interop is verified bidirectionally against [`@envoys/sdk`](https://github.com/jschoemaker/Envoys-public/tree/main/packages/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
