The Problem
AI agents are becoming autonomous participants: booking travel, managing finances, negotiating on our behalf. Every service they touch faces the same question: “Is this agent backed by a real person, or is it a bot?”
Without a standard, every platform builds its own verification. Fragmented, expensive, and unreliable. Proof-of-human gives agents a portable credential that any service can check instantly, without knowing who the human is.
How It Works
Trustless
On-chain verification with no central authority. Any contract can read the registry directly.
Private
ZK proofs reveal nothing about the human's identity. Only a nullifier is stored.
Composable
A single registry call integrates into any EVM contract, backend service, or agent framework.
Sybil-resistant
Each human maps to a unique nullifier, preventing one person from registering unlimited agents.
Security Model
The registry supports four registration modes. All produce the same on-chain result (a verified, sybil-resistant agent NFT) but they differ in who holds the agent's private key and how the human manages their agent.
Agent Identity
recommendedIndependent Agent Key
The agent generates its own keypair. During registration, the agent signs a challenge proving it controls the key. The human proves humanity via Self, and the agent proves key ownership via ECDSA — both in a single QR scan.
How it's secured:
- ECDSA signature in registration proves agent key ownership
- ZK proof binds human identity to nullifier
- Agent signs requests with its own key — human wallet never exposed
Best for: Multiple agents per user, key rotation, delegation, autonomous agents that operate independently.
Verified Wallet
Wallet = Agent Identity
The human's wallet address becomes the agent key. No extra keypair to manage. Ideal for single-agent setups and quick integrations.
How it's secured:
- Key is derived inside the contract callback, can't be spoofed
- ZK proof binds wallet address to human nullifier
- SDK signs requests with wallet key; services recover signer from ECDSA signature
Best for: Single agent per user, quick setup, on-chain gating where msg.sender is the agent.
No Wallet
Agent EOA Owns Its NFT
No crypto wallet required. A fresh agent keypair is generated in the browser, and the agent's own address owns the NFT. An optional guardian can be set for recovery. The user manages the raw private key.
How it's secured:
- Agent signs challenge with its own key during registration
- ZK proof binds human identity to nullifier
- Deregister anytime by scanning passport again
Best for: Non-crypto users who just need an agent registered quickly with their passport.
Smart Wallet
Passkey + Kernel Smart Account
A passkey (Face ID / fingerprint) creates a Kernel smart account as guardian. No MetaMask, no seed phrase. The agent still has its own ECDSA key for signing requests; the smart wallet handles on-chain management gaslessly via Pimlico.
How it's secured:
- Passkey (WebAuthn) backed by device biometrics, phishing-resistant
- Smart wallet = guardian, can revoke agent gaslessly
- Agent signs requests with its own ECDSA key
Best for: Users who want the simplest experience with no seed phrases, no browser extensions, and gasless management.
ZK-Attested Credentials
Agents can optionally carry ZK-attested claims from their human backer, such as age verification (over 18), OFAC sanctions clearance, nationality, or name. During registration, the user chooses which fields to disclose. The Self app generates a zero-knowledge proof on the user's phone. Only the attested result is stored on-chain, never raw passport data.
Any service can query an agent's credentials on-chain or via the SDK. No additional identity check needed. Unselected fields are simply not included. All disclosures are fully optional and chosen by the user at registration time.
Off-Chain: Request Signing
The on-chain registry proves “this address is human-backed.” But when an agent makes an API call, the service needs to prove “this request actually came from that address.” Without this, anyone could claim to be a registered agent.
The SDK solves this with ECDSA request signing. In both modes, the flow is the same:
Agent Side
Signs each request with the agent's private key (wallet key in simple mode, independent key in advanced mode). The signature covers the timestamp, HTTP method, URL, and body hash, preventing replay and tampering.
Service Side
Recovers the signer address from the ECDSA signature (cryptographic, can't be faked), converts it to a bytes32 key, and checks isVerifiedAgent() on-chain.
The signer's identity is recovered from the signature itself, never trusted from a header. This closes the off-chain verification gap completely.
Fully composable. SDKs are available for TypeScript, Python, and Rust, with the signing protocol open for raw implementations in any language. Sign requests in Python, verify in Rust, or vice versa. The signing protocol is language-agnostic — all SDKs produce identical signatures.
Sybil Resistance
Each human gets a unique, privacy-preserving nullifier derived from their passport. The registry tracks how many agents share each nullifier. Services can enforce their own limits:
Strict (max 1)
One agent per human. Best for governance voting, airdrops, and any context where uniqueness matters.
Moderate (max N)
Allow a few agents per human. Good for agent marketplaces where one person might run multiple bots.
Detection only
Allow unlimited but flag duplicates with sameHuman(). Good for analytics and reputation.
A2A Agent Cards & Reputation Scoring
Every registered agent gets an A2A-compatible identity card with a trust score backed by on-chain verification.
Verification Strength Scale
The score comes from the proof provider that verified the agent, not computed client-side. Self Protocol uses passport/biometric NFC verification (strength 100).
For Developers: Reputation-Based Access Control
Use the HTTP API to check an agent's verification strength before granting access.
// Quick check: Only accept passport-verified agents
const baseUrl = "https://self-agent-id.vercel.app"; // replace with your deployment URL
const res = await fetch(`${baseUrl}/api/reputation/42220/${agentId}`);
const { score, proofType } = await res.json();
if (score < 100) {
throw new Error("Agent must be verified with passport");
}// Tiered access based on verification strength
const accessLevel = score >= 100 ? "full"
: score >= 80 ? "standard"
: score >= 60 ? "limited"
: "rejected";// On-chain: Use SelfReputationProvider directly SelfReputationProvider rep = SelfReputationProvider(0x...); uint8 score = rep.getReputationScore(agentId); require(score >= 80, "Insufficient verification");
ERC-8004: Three Registries
Self Protocol covers all three registry types defined by ERC-8004:
SelfAgentRegistry
Agent NFT + human proof + ZK-attested credentials
SelfReputationProvider
Verification strength score from proof providers
SelfValidationProvider
Real-time proof status + freshness check
Interface Specification
This extension adds proof-of-human capabilities to the ERC-8004 Agent Registry standard. The additions are shown below.
ERC-8004 Base Standard
The base agent registry that every ERC-8004 implementation provides.
1/// @title IERC8004 - Agent Registry (Base Standard)2interface IERC8004 {3 function registerAgent(bytes32 agentPubKey) external returns (uint256);4 function getAgentId(bytes32 agentPubKey) external view returns (uint256);5 function ownerOf(uint256 agentId) external view returns (address);6}
Proof-of-Human Extension
These functions are added on top of ERC-8004 to provide human-verification guarantees. Any protocol can query these to check if an agent is backed by a verified human.
1/// @title IERC8004ProofOfHuman - Extension Interface2/// @notice Adds proof-of-human verification to ERC-8004 agents.3interface IERC8004ProofOfHuman is IERC8004 {4 // ── Registration ──────────────────────────────5 function registerWithHumanProof(6 string calldata agentMetadata,7 address proofProvider,8 bytes calldata proof,9 bytes calldata providerData10 ) external returns (uint256 agentId);1112 function revokeHumanProof(13 uint256 agentId,14 address proofProvider,15 bytes calldata proof,16 bytes calldata providerData17 ) external;1819 // ── Verification (read by any service/contract) ─20 function isVerifiedAgent(bytes32 agentPubKey) external view returns (bool);21 function hasHumanProof(uint256 agentId) external view returns (bool);22 function getHumanNullifier(uint256 agentId) external view returns (uint256);23 function getProofProvider(uint256 agentId) external view returns (address);2425 // ── Sybil detection ───────────────────────────26 function getAgentCountForHuman(uint256 nullifier) external view returns (uint256);27 function sameHuman(uint256 a, uint256 b) external view returns (bool);28}
IHumanProofProvider
Pluggable interface for identity verification backends. Self Protocol is the reference provider; any ZK identity system can implement this.
1/// @title IHumanProofProvider2/// @notice Pluggable identity backend for proof-of-human.3interface IHumanProofProvider {4 /// @notice Verify a ZK proof and return (success, nullifier).5 function verifyHumanProof(6 bytes calldata proof,7 bytes calldata providerData8 ) external returns (bool verified, uint256 nullifier);910 /// @notice Human-readable provider name (e.g. "Self Protocol").11 function providerName() external view returns (string memory);1213 /// @notice Verification strength score (0-100).14 function verificationStrength() external view returns (uint256);15}