acdp — Python SDK

Python bindings for ACDP (acdp-py)

Thin PyO3 binding over the acdp Rust library. Implements the producer- and consumer-side crypto for the Agent Context Distribution Protocol — v0.1.0 core plus the v0.2.0 Trust & Hardening surface (RFC-ACDP-0001 through 0015). HTTP is intentionally left to the caller — pair this with httpx / requests for transport.

Package version tracks the binding release line (currently 0.8.0).

Install

pip install acdp               # from PyPI

Install (development)

pip install maturin
maturin develop                # editable install into the active venv
pytest tests/                  # in-process unit tests, no HTTP

Build a wheel

maturin build --release        # produces target/wheels/acdp-*.whl
pip install target/wheels/acdp-*.whl

Verification surface

Beyond build_publish_request / build_supersede_request (Ed25519 and P-256 producers) and the verify_content_hash / verify_signature basics, AcdpVerifier exposes the full 0.2.0 surface:

  • verify_body_offline / verify_publish_request_offline — offline did:key verification (no network).
  • verify_receipt / verify_lineage_head_receipt — registry receipts (RFC-ACDP-0010/0011).
  • verify_log_checkpoint / verify_log_inclusion / verify_log_consistency — transparency log (RFC-ACDP-0012).
  • verify_lifecycle_event — lifecycle/retraction (RFC-ACDP-0013).
  • parse_key_revocation / classify_under_revocation — key revocation (RFC-ACDP-0014).
  • build_witness_cosignature / verify_witness_cosignature / evaluate_witness_quorum — witness cosigning (RFC-ACDP-0015).

AcdpDid, AcdpDidDocument, AcdpCanonicalizer, AcdpMerkle, and AcdpSsrfPolicy are also exported.

Quickstart

import json, acdp

producer = acdp.AcdpProducer.generate(
    "did:web:agents.example.com:my-agent",
    "did:web:agents.example.com:my-agent#key-1",
)

raw = producer.build_publish_request(
    title="Q1 snapshot",
    context_type="data_snapshot",
    summary="Quarter-end inventory",
)
request = json.loads(raw)

# POST `raw` (the JSON string) to the registry's /v1/contexts endpoint
# with your HTTP client of choice. On retrieve, validate the response:
body = ...  # response.json()["body"]
acdp.AcdpVerifier.verify_content_hash(json.dumps(body), body["content_hash"])
acdp.AcdpVerifier.verify_signature(
    pub_key_b64,                  # resolved from the producer's did:web doc
    body["signature"]["value"],
    body["content_hash"],
)

Design rules

  • JSON across the FFI boundary. Every method accepts and returns JSON strings — never a Rust type, never a Python dataclass. The wheel stays at ~500 lines of glue.
  • Crypto in Rust, HTTP in Python. Key generation, JCS + SHA-256 hashing, Ed25519 signing, and signature verification all happen in the underlying acdp crate. The Python side handles transport, retries, and observability.
  • AcdpProducer stores a 32-byte seed. The Rust SigningKey is ZeroizeOnDrop and not Clone, so the binding rebuilds the signing key from the seed on each call.
  • Golden vector parity. test_golden_content_hash pins the Python-side content_hash and signature.value against the spec's sig-001 fixture — the same constants the Rust suite asserts. A drift on either side is a protocol break.

Layout

bindings/acdp-py/
├── Cargo.toml         # standalone [workspace]; depends on `acdp` via path
├── pyproject.toml     # maturin build backend
├── README.md          # this file
├── src/
│   ├── lib.rs         # #[pymodule] entry point
│   ├── producer.rs    # AcdpProducer: build/sign publish requests
│   ├── verifier.rs    # AcdpVerifier: content_hash + signature verify
│   └── helpers.rs     # visibility / context_type string parsers
└── tests/
    └── test_producer.py