Post-quantum cryptographic identity for AI agents — Python SDK
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/sdkon npm.
Both SDKs share the same credential schema, so credentials are mutually inspectable across languages.
pip install cordprotocolRequires Python 3.9+ and depends only on cryptography and pydantic.
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"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 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 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 |
from cordprotocol import is_expired
print(is_expired(cred)) # False (for a freshly issued credential)from cordprotocol import has_permission, SCOPES
print(has_permission(cred, SCOPES.WRITE)) # True | FalseStandard 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 |
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().
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 hereSee examples/langchain_example.py for the full runnable demo.
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.
# 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.jsoncordprotocol v0.3.0 adds full W3C DID and Verifiable Credential support, compatible with the TypeScript SDK v0.4.0.
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"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"] → NoneVerification is fully offline — the issuer's public key is recovered from the did:key embedded in proof.verification_method.
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"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>"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")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)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 base64The 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.
The CordProtocol client wraps the core SDK with optional registry auto-posting and revocation checking against the live API at https://api.cordprotocol.dev.
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.validfrom 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)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}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- Website: cordprotocol.dev
- npm package:
@cordprotocol/sdk - Issues: GitHub Issues
MIT