Skip to main content

Post-quantum cryptographic identity for AI agents

Project description

cordprotocol

Post-quantum cryptographic identity for AI agents — Python SDK

PyPI version Python 3.9+ License: MIT

Cord Protocol gives every AI agent a cryptographically signed identity credential so that tools, services, and other agents can verify who is calling before acting. The Python SDK is a first-class implementation designed for developers building LangChain, AutoGen, and CrewAI agents.

TypeScript developer?
The JavaScript/TypeScript SDK is @cordprotocol/sdk on npm.
Both SDKs share the same credential schema, so credentials are mutually inspectable across languages.


Installation

pip install cordprotocol

Requires Python 3.9+ and depends only on cryptography and pydantic.


Quick start

import os
from cordprotocol import CordProtocol, CordProtocolConfig, generate_keypair, SCOPES

kp = generate_keypair()  # generate once, store securely

cord = CordProtocol(CordProtocolConfig(
    registry=True,
    api_key=os.environ.get("CORD_API_KEY"),
))

# Issue a signed credential — automatically registered in the public trust network
cred = cord.issue_credential(
    agent_id="my-agent-001",
    issued_to="acme-corp",
    permissions=[SCOPES.READ, SCOPES.EXECUTE],
    expires_in="24h",
    private_key=kp.private_key,
)

# Verify signature, expiry, and revocation status
result = cord.verify_credential(cred)
assert result.valid
print(result.credential.agent_id)  # "my-agent-001"

API reference

generate_keypair(backend=None) -> KeyPair

Generate a new cryptographic keypair.

from cordprotocol import generate_keypair

kp = generate_keypair()
print(kp.algorithm)    # "Ed25519"
print(kp.public_key)   # base64-encoded public key
print(kp.private_key)  # base64-encoded private key (keep secret)
Field Type Description
private_key str Base64-encoded private key
public_key str Base64-encoded public key
algorithm str Algorithm identifier, e.g. "Ed25519"

issue_credential(...) -> AgentCredential

Issue a signed identity credential for an AI agent.

from cordprotocol import issue_credential, SCOPES

cred = issue_credential(
    agent_id="langchain-prod-001",     # unique agent identifier
    issued_to="acme-corp",             # recipient (user, org, etc.)
    permissions=[SCOPES.READ, SCOPES.WRITE],
    expires_in="7d",                   # "30m" | "24h" | "7d" | "30d" …
    private_key=kp.private_key,
    attestation_hash=None,             # optional SHA-256 of audit doc
)
Parameter Type Description
agent_id str Unique identifier for the agent
issued_to str Entity receiving the credential
permissions List[str] Permission scopes (see SCOPES)
expires_in str Duration: m minutes, h hours, d days
private_key str Base64-encoded issuer private key
attestation_hash Optional[str] SHA-256 hash of an attestation document

verify_credential(credential, backend=None) -> VerificationResult

Verify a credential's signature and expiry.

from cordprotocol import verify_credential

result = verify_credential(cred)

if result.valid:
    print(f"Agent {result.credential.agent_id} verified")
else:
    print(f"Rejected: {result.error}")
Field Type Description
valid bool True if signature and expiry are OK
error Optional[str] Reason for failure when valid=False
credential Optional[AgentCredential] The credential on success

is_expired(credential) -> bool

from cordprotocol import is_expired
print(is_expired(cred))  # False (for a freshly issued credential)

has_permission(credential, scope) -> bool

from cordprotocol import has_permission, SCOPES
print(has_permission(cred, SCOPES.WRITE))  # True | False

SCOPES

Standard permission scopes, compatible with the TypeScript SDK:

Constant Value Purpose
SCOPES.READ "read:data" Read access to data sources
SCOPES.WRITE "write:data" Write / mutate data
SCOPES.EXECUTE "execute:actions" Trigger external actions
SCOPES.COMMUNICATE "communicate:agents" Talk to other agents
SCOPES.SPEND "spend:budget" Authorise spending operations

AgentCredential

Pydantic model — schema identical to the TypeScript AgentCredential.

Field Type Description
id str UUID v4
agent_id str Agent identifier
issued_to str Recipient
issued_at datetime UTC issue time
expires_at datetime UTC expiry time
permissions List[str] Granted scopes
attestation_hash Optional[str] Optional audit hash
issuer_public_key str Base64 public key
signature str Base64 signature

Serialisation helpers: .to_dict(), .from_dict(), .to_json(), .from_json().


Integration examples

LangChain

from cordprotocol import generate_keypair, issue_credential, verify_credential, SCOPES

ISSUER_KP = generate_keypair()  # generate once, store in your KMS

def make_agent_credential(agent_id: str):
    return issue_credential(
        agent_id=agent_id,
        issued_to="langchain-runtime",
        permissions=[SCOPES.READ, SCOPES.EXECUTE],
        expires_in="1h",
        private_key=ISSUER_KP.private_key,
    )

class CordProtectedTool(BaseTool):  # from langchain.tools
    name = "my_tool"
    description = "..."

    def __init__(self, credential):
        super().__init__()
        self.credential = credential

    def _run(self, query: str) -> str:
        result = verify_credential(self.credential)
        if not result.valid:
            raise PermissionError(f"Identity check failed: {result.error}")
        if not has_permission(self.credential, SCOPES.READ):
            raise PermissionError("Missing read:data permission")
        # ... your tool logic here

See examples/langchain_example.py for the full runnable demo.

CrewAI

from cordprotocol import generate_keypair, issue_credential, SCOPES

registry_kp = generate_keypair()

def register_crew_agent(agent_id: str, permissions: list):
    return issue_credential(
        agent_id=agent_id,
        issued_to="crewai-runtime",
        permissions=permissions,
        expires_in="2h",
        private_key=registry_kp.private_key,
    )

See examples/crewai_example.py for the full trust-registry pattern.


CLI

# Generate a keypair
cord keygen

# Issue a credential
cord issue \
  --agent-id my-agent \
  --issued-to acme \
  --permissions read:data,write:data \
  --expires-in 24h \
  --private-key <base64-private-key>

# Verify a saved credential
cord verify credential.json

DID & Verifiable Credentials (W3C Standard)

cordprotocol v0.3.0 adds full W3C DID and Verifiable Credential support, compatible with the TypeScript SDK v0.4.0.

Issue a Verifiable Credential

from cordprotocol import generate_keypair, issue_verifiable_credential, agent_id_to_did

kp = generate_keypair()

vc = issue_verifiable_credential(
    agent_id="trading-agent",
    issued_to="paul@example.com",
    permissions=["read:market", "execute:trades"],
    expires_in="24h",
    private_key=kp.private_key,
    issuer_did="did:web:cordprotocol.dev",
)

print(vc.id)        # "urn:uuid:<uuid>"
print(vc.issuer)    # "did:web:cordprotocol.dev"
print(vc.proof.type)  # "Ed25519Signature2020"

Verify a Verifiable Credential

from cordprotocol import verify_verifiable_credential

result = verify_verifiable_credential(vc)
# result["valid"]       → True
# result["agent_id"]    → "trading-agent"
# result["permissions"] → ["read:market", "execute:trades"]
# result["reason"]      → None

Verification is fully offline — the issuer's public key is recovered from the did:key embedded in proof.verification_method.

DID utilities

from cordprotocol import agent_id_to_did, did_to_agent_id

did = agent_id_to_did("trading-agent")
# → "did:web:cordprotocol.dev:agents:trading-agent"

agent_id = did_to_agent_id(did)
# → "trading-agent"

Create a DID Document

from cordprotocol import generate_keypair, create_did_document

kp = generate_keypair()
doc = create_did_document(
    did="did:web:cordprotocol.dev:agents:my-agent",
    public_key=kp.public_key,
    service_endpoint="https://my-agent.example.com",
)

print(doc.verification_method[0].type)  # "Ed25519VerificationKey2020"
print(doc.verification_method[0].public_key_multibase)  # "z<base58btc>"

Resolve a DID

from cordprotocol import resolve_did, resolve_did_sync

# did:key — offline, no network
result = resolve_did_sync("did:key:z6Mk...")
doc = result.did_document

# did:web — fetches https://<domain>/.well-known/did.json or path equivalent
result = await resolve_did("did:web:cordprotocol.dev:agents:trading-agent")

Convert between AgentCredential and VC

from cordprotocol import (
    issue_credential, agent_credential_to_vc, vc_to_agent_credential_dict,
    generate_keypair, SCOPES,
)

kp = generate_keypair()
cred = issue_credential(
    agent_id="my-agent", issued_to="alice",
    permissions=[SCOPES.READ], expires_in="24h",
    private_key=kp.private_key,
)

# Convert to W3C VC format
vc = agent_credential_to_vc(cred, issuer_did="did:web:cordprotocol.dev")

# Convert back to AgentCredential-compatible dict
d = vc_to_agent_credential_dict(vc)

Key encoding utilities

from cordprotocol import public_key_to_multibase, multibase_to_public_key

multibase = public_key_to_multibase(kp.public_key)   # "z<base58btc>"
base64_key = multibase_to_public_key(multibase)       # original base64

Post-quantum roadmap

The SDK is designed for a seamless upgrade to CRYSTALS-Dilithium (NIST PQC standard). Every location that needs to change is marked with [PQ SWAP POINT] in the source. The swap requires changing one line:

# cordprotocol/crypto/signatures.py
default_backend: CryptoBackend = DilithiumBackend()  # was Ed25519Backend()

No changes are needed in application code.


Hosted API integration

The CordProtocol client wraps the core SDK with optional registry auto-posting and revocation checking against the live API at https://api.cordprotocol.dev.

Basic usage (unchanged)

from cordprotocol import generate_keypair, issue_credential, verify_credential, SCOPES

kp = generate_keypair()

cred = issue_credential(
    agent_id="my-agent",
    issued_to="paul@example.com",
    permissions=["read:data"],
    expires_in="24h",
    private_key=kp.private_key,
)

result = verify_credential(cred)
assert result.valid

With registry and revocation (new in v0.2.0)

from cordprotocol import CordProtocol, CordProtocolConfig, generate_keypair, SCOPES

kp = generate_keypair()

cord = CordProtocol(CordProtocolConfig(
    registry=True,
    api_key=os.environ.get("CORD_API_KEY"),
))

# Issues the credential AND automatically registers the public key
# in the Cord Protocol registry.  Registry failure is silent.
cred = cord.issue_credential(
    agent_id="my-agent",
    issued_to="paul@example.com",
    permissions=["read:data", "write:orders"],
    expires_in="24h",
    private_key=kp.private_key,
)

# Verifies signature + expiry AND checks revocation status via the API.
result = cord.verify_credential(cred)
if not result.valid:
    print(result.error)  # e.g. "Credential has been revoked."

# Revoke a credential (requires api_key)
cord.revoke_credential(cred.id, cred.agent_id, reason="decommissioned")

# Look up a registered agent
registration = cord.lookup_agent("my-agent")
if registration:
    print(registration.active, registration.credential_count)

Low-level registry functions

All registry functions are also available directly, as both async and sync variants:

from cordprotocol import (
    register_agent, register_agent_sync,
    lookup_agent, lookup_agent_sync,
    check_revocation_status, check_revocation_status_sync,
    revoke_credential, revoke_credential_sync,
)

# Async (use inside async functions)
registration = await register_agent("my-agent", kp.public_key, "paul@example.com")
status = await check_revocation_status(cred.id)

# Sync (use in regular code)
registration = register_agent_sync("my-agent", kp.public_key, "paul@example.com")
status = check_revocation_status_sync(cred.id)
# {"revoked": False, "revoked_at": None, "reason": None}

Error handling

from cordprotocol import RegistryError, RevocationError

try:
    await register_agent("my-agent", kp.public_key, "alice")
except RegistryError as e:
    print(f"Registry error: {e}")

try:
    cord.revoke_credential(cred.id, cred.agent_id)
except RevocationError as e:
    print(f"Revocation failed: {e}")
except ValueError as e:
    print(f"Config error: {e}")  # no api_key set

Links


License

MIT

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

cordprotocol-0.3.1.tar.gz (43.3 kB view details)

Uploaded Source

Built Distribution

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

cordprotocol-0.3.1-py3-none-any.whl (28.1 kB view details)

Uploaded Python 3

File details

Details for the file cordprotocol-0.3.1.tar.gz.

File metadata

  • Download URL: cordprotocol-0.3.1.tar.gz
  • Upload date:
  • Size: 43.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for cordprotocol-0.3.1.tar.gz
Algorithm Hash digest
SHA256 0c67b5dd2cd3d97135d57e9dd23112274fbe8126651647bb31f1c63e1bb99682
MD5 3e92061ef12ab5239f1a2b3f6befa296
BLAKE2b-256 38fcfc119f410fc5617f8c4a44b2d65d8a6cc3c1ea0b0f6c90a8a35e969d1d19

See more details on using hashes here.

File details

Details for the file cordprotocol-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: cordprotocol-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 28.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for cordprotocol-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 025c697bcfe3e262ee1cbc445f55d9c285ee7a39f8df7ef403fac448060a3844
MD5 24ef6188eab590076d9c0a84ee56ce75
BLAKE2b-256 fab07b16699b63accbd6674e9920d145b835459ee44a5447255d1e8f55d123c9

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