Skip to main content

Cryptographic trust protocol for human-agent and agent-agent interactions — Python reference SDK.

Project description

ratify-protocol

Python reference SDK for the Ratify Protocol v1 — a cryptographic trust protocol for human-agent and agent-agent interactions as agents start to transact.

Quantum-safe by design: every signature is hybrid Ed25519 + ML-DSA-65 (NIST FIPS 204). Both must verify.

Byte-identical interoperability with the Go, TypeScript, and Rust reference implementations. Validated against the 59 canonical test vectors on every CI run.

Install

pip install ratify-protocol

This pulls in two binary dependencies: cryptography (Ed25519 via OpenSSL) and pqcrypto>=0.3.4 (ML-DSA-65). Both ship wheels for Linux / macOS / Windows on CPython 3.10+.

Running the conformance suite from a clean checkout

If you cloned the repo and want to run python -m pytest against the committed fixtures, the package is not on your path until you install it. Do this:

cd sdks/python
python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'              # installs ratify-protocol + cryptography + pqcrypto + pytest
python -m pytest tests/              # runs 59/59 conformance fixtures

If pqcrypto fails to install (typical on older pip), upgrade pip first:

pip install --upgrade pip
pip install -e '.[dev]'

pqcrypto requires a C compiler toolchain for source builds; prebuilt wheels exist for most platform / Python combinations.

Quickstart

from ratify_protocol import (
    generate_human_root, generate_agent,
    DelegationCert, ProofBundle, VerifyOptions,
    PROTOCOL_VERSION, SCOPE_MEETING_ATTEND,
    issue_delegation, sign_challenge, generate_challenge,
    derive_id, verify_bundle, HybridSignature,
)
import time

# 1. DELEGATE — Alice creates her root and authorizes an agent.
root, root_priv = generate_human_root()
agent, agent_priv = generate_agent("Alice's Assistant", "voice_agent")

now = int(time.time())
cert = DelegationCert(
    cert_id="cert-1", version=PROTOCOL_VERSION,
    issuer_id=root.id, issuer_pub_key=root.public_key,
    subject_id=agent.id, subject_pub_key=agent.public_key,
    scope=[SCOPE_MEETING_ATTEND],
    issued_at=now, expires_at=now + 7 * 24 * 3600,
    signature=HybridSignature(ed25519=b"", ml_dsa_65=b""),  # filled by issue_delegation
)
issue_delegation(cert, root_priv)

# 2. PRESENT — agent builds a proof bundle on demand.
challenge = generate_challenge()
challenge_at = int(time.time())
bundle = ProofBundle(
    agent_id=agent.id,
    agent_pub_key=agent.public_key,
    delegations=[cert],
    challenge=challenge,
    challenge_at=challenge_at,
    challenge_sig=sign_challenge(challenge, challenge_at, agent_priv),
)

# 3. VERIFY — any third party checks the bundle.
result = verify_bundle(bundle, VerifyOptions(required_scope=SCOPE_MEETING_ATTEND))
if result.valid:
    print(f"✅ Authorized agent {result.agent_id} for {result.human_id}, scope={result.granted_scope}")
else:
    print(f"❌ {result.identity_status}: {result.error_reason}")

Key custody

The protocol supports three key-custody modes with different trust tradeoffs. See SPEC.md §15.2 for the full model.

Self-custody (strongest)

The user generates and holds their own keypair. No third party can sign on their behalf.

from ratify_protocol import generate_human_root, issue_delegation

# User generates keypair on their own device — private key never leaves
root, private_key = generate_human_root()

# User signs delegations locally
issue_delegation(cert, private_key)

# Only root.id and root.public_key are shared with registries

Custodial

A registry operator generates and stores the keypair server-side (envelope-encrypted with KMS). The user never touches keys directly. The operator calls the same SDK functions on the user's behalf.

Self-custody upgrade

A user who started in custodial mode can migrate to self-custody at any time using KeyRotationStatement:

from ratify_protocol import (
    generate_human_root,
    issue_key_rotation_statement,
    KeyRotationStatement,
)

# User generates a NEW keypair on their device
new_root, new_private_key = generate_human_root()

# Rotation statement signed by BOTH old (custodial) and new (device) keys
stmt = KeyRotationStatement(
    version=1,
    old_id=old_root.id,
    old_pub_key=old_root.public_key,
    new_id=new_root.id,
    new_pub_key=new_root.public_key,
    rotated_at=int(time.time()),
    reason="routine",
)
issue_key_rotation_statement(stmt, old_custodial_private_key, new_private_key)

# From now on, only the user's device key can sign delegations.
# Auditors verify continuity via the rotation statement.

Canonical serialization

from ratify_protocol import canonical_json, delegation_sign_bytes, challenge_sign_bytes

These produce byte-identical output to the Go / TS / Rust references. If your application needs to sign Ratify artifacts with custom code, always pass through canonical_json for the JSON pieces.

Scope vocabulary

from ratify_protocol import (
    SCOPE_MEETING_ATTEND,     # "meeting:attend"
    SCOPE_FILES_WRITE,         # sensitive — never rides a wildcard
    expand_scopes,
    intersect_scopes,
    is_sensitive,
    validate_scopes,
)

expand_scopes(["meeting:*"])
# ['meeting:attend', 'meeting:chat', 'meeting:share_screen', 'meeting:speak', 'meeting:video']

intersect_scopes(["meeting:*"], ["meeting:attend", "meeting:speak"])
# ['meeting:attend', 'meeting:speak']

Full scope vocabulary at a glance

Ratify v1 ships 52 canonical scopes across fourteen domains, plus a custom: extension pattern for application-specific scopes. See SPEC.md §9 for the full table including sensitivity flags and wildcard expansions.

For app-specific needs not covered by the canonical vocabulary, use the custom: prefix:

from ratify_protocol import CUSTOM_SCOPE_PREFIX, validate_scopes

validate_scopes(["custom:acme:inventory:read"])  # → None (valid)

Custom scopes pass through expand_scopes unchanged and are non-sensitive by default.

Running the conformance tests

From this SDK directory:

python -m venv .venv && source .venv/bin/activate
pip install -e .
pip install pytest
pytest -v

The suite loads every fixture at ../../testvectors/v1/*.json and runs it through the Python implementation. All 59 must pass; any failure means this SDK has drifted from the Go reference.

Notes on the ML-DSA-65 library

This SDK uses pqcrypto which wraps PQClean's ML-DSA-65 implementation. Two things to be aware of:

Randomized signing. pqcrypto's default signing mode is randomized (two signings of the same message produce different bytes). This does NOT affect interop: signatures produced here verify correctly in Go, TS, and Rust implementations, and vice versa. The canonical signable bytes (what gets fed into the signature function) are what must match across languages — those do match byte-for-byte.

Non-deterministic keygen from seeds. pqcrypto does not expose seed-based ML-DSA-65 key generation through its public API — crypto_sign_keypair reads from the OS RNG internally. This means hybrid_keypair_from_seeds() is NOT truly deterministic on the ML-DSA side in Python. The practical consequence: Python cannot regenerate the canonical test fixtures (the Go reference does that). Python's conformance contract is verification-only — it verifies Go-generated fixtures byte-for-byte but does not regenerate them. This is a known limitation of the pqcrypto library, not a protocol limitation.

License

Apache-2.0. See the project-level LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ratify_protocol-1.0.0a5.tar.gz (32.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ratify_protocol-1.0.0a5-py3-none-any.whl (29.1 kB view details)

Uploaded Python 3

File details

Details for the file ratify_protocol-1.0.0a5.tar.gz.

File metadata

  • Download URL: ratify_protocol-1.0.0a5.tar.gz
  • Upload date:
  • Size: 32.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for ratify_protocol-1.0.0a5.tar.gz
Algorithm Hash digest
SHA256 eb5778594dafd87f34876bf205df62b873601b77e64eb111a3065862c07f01c2
MD5 ebce0a9d5fb3fdac634e28d4e6b330e9
BLAKE2b-256 6086d67a7a58a3b44e309af7e1eb1e543e6f457c4f02bf1e924ba3a2b79125d8

See more details on using hashes here.

File details

Details for the file ratify_protocol-1.0.0a5-py3-none-any.whl.

File metadata

File hashes

Hashes for ratify_protocol-1.0.0a5-py3-none-any.whl
Algorithm Hash digest
SHA256 61a59f035350a6854f7b24f3d2a02fe5d55bf79a2b119d185211cc2ae5245e3f
MD5 7be0b4adba526a6c56dce31fb12bee07
BLAKE2b-256 1665216cc4b78e499bf333a423a589450473bf1d6d6d3cfa3570fd3ab02d60a1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page