@reykzett/agentid
v0.1.0
Published
Cryptographic identity for AI agents — verifiable reputation, delegation chains, and agent-to-agent trust
Maintainers
Readme
AgentID
Cryptographic identity for AI agents — verifiable reputation, delegation chains, and agent-to-agent trust.
AI agents have no verifiable identity. When an agent claims "I am Claude, assistant of Company X" — nobody can verify that. There is no trust, no reputation, no accountability. AgentID solves this with cryptographic identities: like SSL certificates, but for agents.
Features
- Self-certifying identity —
did:keyaddressing, no registry needed - Ed25519 signatures — sign and verify any agent action
- Delegation chains — Human -> Agent A -> Agent B, each link cryptographically signed
- Scoped authority — action-based permissions with mandatory expiry
- Revocation — revoke delegations before expiry, checked across all verify paths
- Attestations — verifiable claims about agent behavior and capabilities
- Private key encapsulation — keys never exposed via public API
- Tiny footprint — 3 runtime dependencies, ~25KB total
Install
npm install agentidRequires Node.js >= 18.
Quick Start
Create an Identity
import { createIdentity } from "agentid";
const { identity, signer } = createIdentity({
type: "agent",
name: "DeployBot",
provider: "acme-corp",
});
console.log(identity.id);
// did:key:z6Mkr1...Sign and Verify an Action
import { createIdentity, signAction, verify } from "agentid";
const { signer } = createIdentity({ name: "DeployBot" });
const action = await signAction(signer, {
action: "deploy",
target: "production",
version: "1.2.3",
});
const result = verify(action);
console.log(result.valid); // true
// Tamper with the payload
action.payload.version = "9.9.9";
const tampered = verify(action);
console.log(tampered.valid); // false
console.log(tampered.error?.code); // "INVALID_SIGNATURE"Delegate Authority
import { createIdentity, createDelegation, verifyDelegation } from "agentid";
const human = createIdentity({ type: "human", name: "Alice" });
const agent = createIdentity({ name: "DeployBot", controller: human.identity.id });
const delegation = await createDelegation({
issuer: human.signer,
subject: agent.identity.id,
scope: {
actions: ["deploy-code", "sign-contract"],
delegatable: false,
},
expiresIn: 3600, // 1 hour
});
const result = verifyDelegation(delegation);
console.log(result.valid); // trueVerify a Delegation Chain
import {
createIdentity,
createDelegation,
signAction,
verifyDelegationChain,
} from "agentid";
// Human -> Agent A -> Agent B
const human = createIdentity({ type: "human", name: "Alice" });
const agentA = createIdentity({ name: "Orchestrator" });
const agentB = createIdentity({ name: "DeployBot" });
// Human delegates to Agent A (re-delegatable)
const certA = await createDelegation({
issuer: human.signer,
subject: agentA.identity.id,
scope: { actions: ["deploy-code"], delegatable: true },
expiresIn: 3600,
});
// Agent A delegates to Agent B
const certB = await createDelegation({
issuer: agentA.signer,
subject: agentB.identity.id,
scope: { actions: ["deploy-code"], delegatable: false },
expiresIn: 1800,
proofs: [certA.payload.id],
});
// Agent B signs an action
const action = await signAction(agentB.signer, {
action: "deploy-code",
target: "production",
}, { delegation: certB });
// Verify the full chain
const proofStore = new Map([[certA.payload.id, certA]]);
const result = await verifyDelegationChain({
action,
delegation: certB,
expectedRoot: human.identity.id,
resolveProof: (id) => Promise.resolve(proofStore.get(id) ?? null),
});
console.log(result.valid); // true
console.log(result.delegationValid); // trueAttestations
import { createIdentity, createAttestation, verifyAttestation } from "agentid";
const reviewer = createIdentity({ type: "human", name: "Alice" });
const agent = createIdentity({ name: "DeployBot" });
const attestation = await createAttestation(
reviewer.signer,
agent.identity.id,
{ type: "behavior_rating", rating: 5, context: "Flawless deployment" },
{ expiresIn: 86400 },
);
const result = verifyAttestation(attestation);
console.log(result.valid); // trueRevoke a Delegation
import {
createIdentity,
createDelegation,
signAction,
verify,
createRevocation,
MemoryRevocationRegistry,
} from "agentid";
const human = createIdentity({ type: "human", name: "Alice" });
const agent = createIdentity({ type: "agent", name: "DeployBot" });
const delegation = await createDelegation({
issuer: human.signer,
subject: agent.identity.id,
scope: { actions: ["deploy-code"], delegatable: false },
expiresIn: 3600,
});
// Agent acts under delegation — valid
const action = await signAction(agent.signer, { action: "deploy-code" }, { delegation });
const registry = new MemoryRevocationRegistry();
console.log((await verify(action, { revocation: registry })).valid); // true
// Human revokes the delegation
const revocation = await createRevocation(human.signer, delegation.payload.id, {
reason: "Agent compromised",
});
await registry.revoke(revocation);
// Same action now fails
console.log((await verify(action, { revocation: registry })).valid); // false
console.log((await verify(action, { revocation: registry })).error?.code); // "REVOKED_DELEGATION"Export and Restore an Identity
import { createIdentity, decryptSigner } from "agentid";
const { identity, signer } = createIdentity({ name: "DeployBot" });
// Export (encrypts private key with passphrase)
const bundle = await signer.export("my-secret-passphrase");
// Later: restore from encrypted bundle
const restored = await decryptSigner(bundle, identity, "my-secret-passphrase");
console.log(restored.identity.id === identity.id); // trueHow It Works
A step-by-step walkthrough of the core flow: identity → delegation → action → verification → revocation.
The Scenario
You're building a system where a human (Alice) authorizes an AI agent (DeployBot) to deploy code. Later, the agent gets compromised and Alice needs to revoke its access immediately — before the delegation expires.
Step 1: Create Identities
import { createIdentity } from "agentid";
const alice = createIdentity({ type: "human", name: "Alice" });
const deployBot = createIdentity({
type: "agent",
name: "DeployBot",
controller: alice.identity.id,
});
console.log(deployBot.identity.id);
// did:key:z6Mk...Each identity gets an Ed25519 key pair and a self-certifying did:key address. The private key is captured in a closure inside the signer object — it can never be read directly, only used to sign.
Step 2: Delegate Authority
import { createDelegation } from "agentid";
const delegation = await createDelegation({
issuer: alice.signer,
subject: deployBot.identity.id,
scope: {
actions: ["deploy-code"],
delegatable: false,
},
expiresIn: 3600, // 1 hour
});Alice creates a signed delegation certificate: "I authorize DeployBot to perform deploy-code for 1 hour." The Ed25519 signature proves it came from Alice. The scope limits what the agent can do. delegatable: false prevents the agent from passing authority to another agent.
Step 3: Agent Acts Under Delegation
import { signAction } from "agentid";
const action = await signAction(
deployBot.signer,
{ action: "deploy-code", target: "production", version: "2.1.0" },
{ delegation },
);DeployBot signs the action with its own key. The delegation certificate is attached, so any verifier can see both who acted and who authorized it.
Step 4: Verify
import { verify } from "agentid";
const result = await verify(action);
console.log(result.valid); // true
console.log(result.delegationValid); // trueverify() checks everything in order:
- Is DeployBot's signature over the action valid?
- Is the attached delegation signed by Alice?
- Does the delegation signer match the claimed issuer? (spoofing protection)
- Is DeployBot the delegation's subject?
- Is the delegation still within its expiry window?
- Is the delegation revoked? (if a
RevocationCheckeris provided) - Does the scope cover the required capability? (if
requiredCapabilityis set)
If any check fails, you get { valid: false, error: { code: "...", message: "..." } }. Verify never throws — invalid input is a normal result, not an exception.
Step 5: Revoke
import { createRevocation, MemoryRevocationRegistry } from "agentid";
const registry = new MemoryRevocationRegistry();
const revocation = await createRevocation(
alice.signer,
delegation.payload.id,
{ reason: "Agent exhibited unexpected behavior" },
);
await registry.revoke(revocation);
const result = await verify(action, { revocation: registry });
console.log(result.valid); // false
console.log(result.error?.code); // "REVOKED_DELEGATION"Alice creates a signed revocation — cryptographically proving she revoked it. From this point, every verify() call against this registry rejects actions based on that delegation. To restore access, create a new delegation.
Step 6: Delegation Chains (optional)
For multi-agent setups: Alice → Orchestrator → DeployBot.
const orchestrator = createIdentity({ type: "agent", name: "Orchestrator" });
// Alice delegates to Orchestrator (re-delegatable)
const certA = await createDelegation({
issuer: alice.signer,
subject: orchestrator.identity.id,
scope: { actions: ["deploy-code"], delegatable: true },
expiresIn: 3600,
});
// Orchestrator delegates to DeployBot (terminal)
const certB = await createDelegation({
issuer: orchestrator.signer,
subject: deployBot.identity.id,
scope: { actions: ["deploy-code"], delegatable: false },
expiresIn: 1800,
proofs: [certA.payload.id],
});
// Verify the full chain back to Alice
const proofStore = new Map([[certA.payload.id, certA]]);
const result = await verifyDelegationChain({
action: await signAction(deployBot.signer, { action: "deploy-code" }, { delegation: certB }),
delegation: certB,
expectedRoot: alice.identity.id,
resolveProof: (id) => Promise.resolve(proofStore.get(id) ?? null),
revocation: registry, // checks revocation at EVERY level
});Chain verification walks upward: DeployBot → Orchestrator → Alice. If any link is expired, revoked, or has an invalid signature, the entire chain fails.
The Flow
Alice (Human)
│
├── createIdentity() → did:key:z6MkAlice...
│
├── createDelegation() → signed certificate
│ │ "DeployBot may deploy-code, 1h"
│ ▼
│ DeployBot (Agent)
│ │
│ ├── signAction() → signed action + delegation attached
│ │
│ ▼
│ Verifier
│ │
│ ├── verify(action, { revocation }) → { valid: true/false }
│ │
│ └── Checks: Signature → Delegation → Expiry → Revocation → Scope
│
└── createRevocation() → immediately invalidates delegationAPI Reference
Identity
| Function | Description |
|----------|-------------|
| createIdentity(opts?) | Create a new identity with fresh Ed25519 key pair |
| resolveDidKey(did) | Resolve a did:key to an IdentityDocument |
| decryptSigner(bundle, identity, passphrase) | Restore a Signer from an encrypted key bundle |
| serializeIdentity(identity) | Canonical JSON string of an identity |
Signing & Verification
| Function | Description |
|----------|-------------|
| signAction(signer, payload, opts?) | Sign a payload with timestamp and optional delegation |
| verify(action, opts?) | Verify a SignedAction (signature, delegation, revocation) — async |
| verifySignedPayload(signed) | Verify the raw signature on a SignedPayload |
Delegation
| Function | Description |
|----------|-------------|
| createDelegation(opts) | Create a signed, scoped, time-limited delegation |
| verifyDelegation(delegation, opts?) | Verify a single delegation certificate — async |
| verifyDelegationChain(opts) | Verify a full delegation chain back to root |
Revocation
| Function | Description |
|----------|-------------|
| createRevocation(signer, delegationId, opts?) | Create a signed revocation for a delegation |
| verifyRevocation(revocation) | Verify a revocation entry's signature |
| Class | Description |
|-------|-------------|
| MemoryRevocationRegistry | In-memory revocation registry (testing / ephemeral) |
| FilesystemRevocationRegistry | Persistent registry in ~/.agentid/revocations/ |
All verification functions accept an optional { revocation: RevocationChecker } to check revocation status.
Attestation
| Function | Description |
|----------|-------------|
| createAttestation(signer, subject, claim, opts?) | Create a signed attestation about an agent |
| verifyAttestation(attestation) | Verify an attestation's signature and expiry |
DID Utilities
| Function | Description |
|----------|-------------|
| didFromPublicKey(pubkey) | Create did:key:z... from 32-byte Ed25519 public key |
| publicKeyFromDid(did) | Extract raw public key from did:key |
| parseDid(input) | Parse and validate a DID string |
| isValidDid(input) | Non-throwing DID validation |
Storage
| Class | Description |
|-------|-------------|
| MemoryStore | In-memory identity store (testing / ephemeral) |
| FilesystemStore | Persistent store in ~/.agentid/ |
Both implement the IdentityStore interface: save(), load(), list(), delete().
Core Concepts
Addressing: did:key
Self-certifying, no registry needed. An Ed25519 public key is encoded as:
did:key:z + base58btc(0xed01 + 32-byte-pubkey)The DID itself contains everything needed to verify signatures.
Signer Abstraction
Private keys are captured in closures — never exposed via the public API. The only way to extract a key is through passphrase-encrypted export (PBKDF2 + AES-256-GCM).
Delegation Certificates
UCAN-inspired, action-scoped delegation with mandatory expiry. Chainable: Human -> Agent A -> Agent B (if delegatable: true). Each link is signed by the issuer.
scope: {
actions: ["deploy-code", "sign-contract"],
resources: ["repo:org/myapp"], // optional
delegatable: false // default
}Verification as Result
verify() returns a VerificationResult with a typed error union — it never throws for invalid signatures. That's a normal outcome, not an exception.
type VerificationError =
| { code: "INVALID_SIGNATURE"; message: string }
| { code: "EXPIRED_DELEGATION"; message: string; expiredAt: string }
| { code: "SCOPE_VIOLATION"; message: string; action: string; allowed: string[] }
| { code: "CHAIN_BROKEN"; message: string; brokenAt: AgentDID }
| { code: "REVOKED_DELEGATION"; message: string; delegationId: string; revokedAt: string }
| { code: "INVALID_DID"; message: string };Security
- Strict Ed25519 —
zip215: falseenforces RFC 8032 / FIPS 186-5 compliance, preventing small-order public key forgery - Signer/issuer consistency — all verification paths check that the envelope signer matches the claimed issuer
- Chain depth limit — delegation chains capped at 32 levels to prevent circular reference attacks
- Canonical signing — RFC 8785 deterministic JSON ensures cross-platform signature reproducibility
- Revocation support — delegations can be revoked before expiry via signed
RevocationEntry; all verify paths support optional revocation checking - Key encapsulation — private keys never leave the
Signerclosure; export requires passphrase encryption
Project Structure
src/
index.ts Public API
types.ts All type definitions
errors.ts Typed error classes
version.ts Protocol version
codec/ Base58, hex, base64url, canonical JSON
did/ did:key encoding/decoding/parsing
crypto/ Key generation, signing, verification
identity/ Identity creation, resolution, serialization
delegation/ Delegation creation, verification, chain walking
revocation/ Revocation creation, verification, registries
attestation/ Attestation creation, verification
storage/ MemoryStore, FilesystemStore
test/ 178 tests across 22 files
examples/ 5 runnable example scriptsDevelopment
npm install
npm test # run 178 tests
npm run build # ESM + CJS + DTS output
npm run typecheck # strict TypeScript checkRun Examples
npx tsx examples/01-create-identity.ts
npx tsx examples/02-sign-and-verify.ts
npx tsx examples/03-delegate-authority.ts
npx tsx examples/04-verify-delegation-chain.ts
npx tsx examples/05-revoke-delegation.tsTech Stack
| Component | Choice | Why |
|-----------|--------|-----|
| Language | TypeScript (ESM + CJS dual) | Agent ecosystem is JS/TS-heavy |
| Crypto | @noble/ed25519 + @noble/hashes | Audited, zero deps, ~20KB |
| Canonicalization | canonicalize (RFC 8785) | Deterministic JSON for signing, ~2KB |
| Build | tsup | ESM + CJS + DTS in one config |
| Test | vitest | Fast, native TS/ESM |
Total runtime deps: 3 packages, ~25KB.
License
MIT
