Skip to content

Python SDK

The Python SDK is byte-for-byte interoperable with Go, TypeScript, and Rust. Python 3.10+. Pure-Python with cryptography and pqcrypto as the only runtime dependencies.

Terminal window
pip install ratify-protocol==1.0.0a6

Or from source:

Terminal window
git clone https://github.com/identities-ai/ratify-protocol
cd ratify-protocol/sdks/python
pip install -e '.[dev]' # the `dev` extra includes pytest
import time
import secrets
from ratify_protocol import (
generate_human_root,
generate_agent,
issue_delegation,
sign_challenge,
verify_bundle,
PROTOCOL_VERSION,
SCOPE_MEETING_ATTEND,
SCOPE_MEETING_SPEAK,
DelegationCert,
ProofBundle,
)
# 1. Alice generates her hybrid root identity.
alice, alice_priv = generate_human_root()
# 2. Her AI agent generates its own hybrid keypair.
agent, agent_priv = generate_agent("Alice's Scheduler", "custom")
# 3. Alice signs a delegation cert.
cert = DelegationCert(
cert_id=secrets.token_hex(8),
version=PROTOCOL_VERSION,
issuer_id=alice.id,
issuer_pub_key=alice.public_key,
subject_id=agent.id,
subject_pub_key=agent.public_key,
scope=[SCOPE_MEETING_ATTEND, SCOPE_MEETING_SPEAK],
issued_at=int(time.time()),
expires_at=int(time.time()) + 7 * 24 * 3600,
)
issue_delegation(cert, alice_priv)
# 4. Verifier issues a challenge. Agent signs it.
challenge = secrets.token_bytes(32)
challenge_at = int(time.time())
challenge_sig = sign_challenge(challenge, challenge_at, agent_priv)
# 5. Agent assembles a proof bundle.
bundle = ProofBundle(
agent_id=agent.id,
agent_pub_key=agent.public_key,
delegations=[cert],
challenge=challenge,
challenge_at=challenge_at,
challenge_sig=challenge_sig,
)
# 6. Verifier runs the verifier.
result = verify_bundle(bundle, required_scope=SCOPE_MEETING_ATTEND)
if result.valid:
print(f"✓ Authorized: {result.agent_id}, granted: {result.granted_scope}")
else:
print(f"✗ Rejected: {result.identity_status}{result.error_reason}")
from ratify_protocol import verify_bundle, IdentityStatus
result = verify_bundle(
bundle,
required_scope="meeting:attend",
required_custom_scope=None,
now_override=None, # Use real time (test only)
revocation_list=signed_revocation_list,
)
# Branch on identity status — fail-closed
if result.identity_status == IdentityStatus.VALID:
accept_request()
elif result.identity_status == IdentityStatus.BAD_SIGNATURE:
reject("tampered")
elif result.identity_status == IdentityStatus.EXPIRED:
reject("expired")
elif result.identity_status == IdentityStatus.SCOPE_DENIED:
reject("insufficient scope")
elif result.identity_status == IdentityStatus.REVOKED:
reject("revoked")
elif result.identity_status == IdentityStatus.REPLAY:
reject("challenge too old")

The signing functions are synchronous (they’re CPU-bound crypto, not I/O). If you’re integrating with an asyncio event loop, wrap them with asyncio.to_thread:

import asyncio
result = await asyncio.to_thread(verify_bundle, bundle, required_scope="api:read")

This frees the event loop while ML-DSA-65 verification runs (~5–10 ms on a modern CPU).

Every public function is fully type-annotated. The SDK passes mypy --strict.

from ratify_protocol import (
DelegationCert,
ProofBundle,
HybridSignature,
HybridPublicKey,
HybridPrivateKey,
HumanRoot,
AgentIdentity,
VerifyResult,
GeoConstraint,
TemporalConstraint,
VersionConstraint,
RevocationList,
)
Terminal window
cd sdks/python
pytest

Expected output: 59 passed. The fixtures are loaded from ../../testvectors/v1/ and run through each function in the SDK. Byte-identical to the Go reference output.