This ERC defines a Trust Registry that enables agents to establish and query transitive trust relationships using ENS names as identifiers. Trust is expressed at four levels (Unknown, None, Marginal, Full) and propagates through signature chains following the GNU Privacy Guard (GnuPG) web of trust model.
The registry serves as the trust and delegation module anticipated by ERC-8001, enabling coordinators to gate participation based on trust graph proximity. An agent is considered valid from a coordinator’s perspective if sufficient trust paths exist between them.
This standard specifies trust attestation structures, the path verification algorithm, ENS integration semantics, and ERC-8001 coordination hooks.
Motivation
ERC-8001 defines minimal primitives for multi-party agent coordination but explicitly defers trust to modules:
“Privacy, thresholds, bonding, and cross-chain are left to modules.”
And in Security Considerations:
“Equivocation: A participant can sign conflicting intents. Mitigate with module-level slashing or reputation.”
This ERC provides that trust and delegation module. Before coordinating, agents need answers to:
“Should I include this agent in my coordination?” — Participant selection
“Can I trust this agent’s judgment about other agents?” — Transitive trust
“How do I update trust based on coordination outcomes?” — Trust maintenance
Why Web of Trust?
The web of trust model, proven over 25+ years in GnuPG, solves the bootstrap problem: how do you establish trust with unknown agents without a centralised registrar?
GnuPG Concept
This Standard
Public key
ENS name
Key signing
Trust attestation
Owner trust levels
TrustLevel enum
Key validity
Agent validity for coordination
Certification path
Trust chain through agents
Why ENS?
ENS provides a battle-tested, finalized identity layer:
Stable identifiers that survive key rotation
Ownership semantics via owner() and isApprovedForAll()
Human readable names (alice.agents.eth not 0x742d...)
Subdomain delegation for protocol-issued agent identities
Using ENS avoids dependency on draft identity standards while remaining compatible with future standards through adapter patterns.
Deployment note: This standard requires access to an ENS registry. On Ethereum mainnet, use the canonical ENS deployment. On other networks, use network-specific ENS deployments or bridges. CCIP-Read is a client-side mechanism and cannot be used for on-chain validation.
Identity Continuity
ENS names are the identity. When an ENS name is transferred, the new owner inherits existing trust relationships where that name is the trustee. The new owner can manage trust where they are the trustor.
Implementations SHOULD use short expiries (RECOMMENDED: 90 days maximum) for high-stakes scopes to limit exposure from name transfers. Agents SHOULD monitor Transfer events on ENS names they trust and re-evaluate trust accordingly.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Overview
This ERC specifies:
Trust levels and their semantics
ENS-indexed trust attestation structures with scope as key
Implementations SHOULD expose the domain via EIP-5267.
Primary Types
structTrustAttestation{bytes32trustorNode;// ENS namehash of trustor
bytes32trusteeNode;// ENS namehash of trustee
TrustLevellevel;// Trust level assigned
bytes32scope;// Scope restriction; bytes32(0) = universal
uint64expiry;// Unix timestamp; 0 = no expiry
uint64nonce;// Per-trustor monotonic nonce
}structValidationParams{uint8maxPathLength;// Maximum trust chain depth (1-10)
TrustLevelminEdgeTrust;// Minimum trust level required on each edge
bytes32scope;// Required scope; bytes32(0) = any
boolenforceExpiry;// Check expiry on all chain elements
bytes32[]requiredAnchors;// Path MUST traverse at least one anchor; empty = no requirement
}structTrustPath{bytes32[]nodes;// [validator, ...intermediaries..., target]
}
Path length definition: Path length is the number of edges (trust relationships) in the path. A direct trust relationship has path length 1. A path [A, B, C] has length 2.
Implementations MUST expose the following interface:
interfaceITrustRegistry{// ═══════════════════════════════════════════════════════════════════
// Events
// ═══════════════════════════════════════════════════════════════════
/// @notice Emitted when trust is set or updated
eventTrustSet(bytes32indexedtrustorNode,bytes32indexedtrusteeNode,TrustLevellevel,bytes32indexedscope,uint64expiry);/// @notice Emitted when trust is explicitly revoked
eventTrustRevoked(bytes32indexedtrustorNode,bytes32indexedtrusteeNode,bytes32indexedscope,bytes32reasonCode);/// @notice Emitted when an identity gate is configured
eventIdentityGateSet(bytes32indexedcoordinationType,bytes32indexedgatekeeperNode,uint8maxPathLength,TrustLevelminEdgeTrust);/// @notice Emitted when an identity gate is removed
eventIdentityGateRemoved(bytes32indexedcoordinationType);// ═══════════════════════════════════════════════════════════════════
// Trust Management
// ═══════════════════════════════════════════════════════════════════
/// @notice Set trust level for another agent in a specific scope
/// @dev Signature MUST be from ENS owner (EOA) or validate via EIP-1271 (contract)
/// @param attestation The trust attestation
/// @param signature EIP-712 signature from trustor's ENS owner
functionsetTrust(TrustAttestationcalldataattestation,bytescalldatasignature)external;/// @notice Batch set multiple trust relationships
/// @dev All attestations MUST share the same trustorNode
/// @param attestations Array of trust attestations
/// @param signatures Corresponding signatures
functionsetTrustBatch(TrustAttestation[]calldataattestations,bytes[]calldatasignatures)external;/// @notice Revoke trust (sets level to None)
/// @dev Caller MUST be ENS owner or approved operator
/// @param trustorNode The trustor's ENS namehash
/// @param trusteeNode The agent to revoke trust from
/// @param scope The scope to revoke trust in
/// @param reasonCode Reason code for revocation
functionrevokeTrust(bytes32trustorNode,bytes32trusteeNode,bytes32scope,bytes32reasonCode)external;/// @notice Get trust record between two agents in a specific scope
/// @param trustorNode The trusting agent
/// @param trusteeNode The trusted agent
/// @param scope The trust scope (bytes32(0) for universal)
/// @return level Current trust level
/// @return expiry Expiration timestamp (0 = never)
functiongetTrust(bytes32trustorNode,bytes32trusteeNode,bytes32scope)externalviewreturns(TrustLevellevel,uint64expiry);/// @notice Get current nonce for a trustor
/// @param trustorNode The agent's ENS namehash
/// @return Current nonce value
functiongetNonce(bytes32trustorNode)externalviewreturns(uint64);// ═══════════════════════════════════════════════════════════════════
// Path Verification
// ═══════════════════════════════════════════════════════════════════
/// @notice Verify a pre-computed trust path
/// @param path The trust path to verify
/// @param params Validation parameters
/// @return valid Whether the path satisfies validation requirements
/// @return anchorSatisfied Whether requiredAnchors constraint is met
functionverifyPath(TrustPathcalldatapath,ValidationParamscalldataparams)externalviewreturns(boolvalid,boolanchorSatisfied);// ═══════════════════════════════════════════════════════════════════
// ERC-8001 Integration
// ═══════════════════════════════════════════════════════════════════
/// @notice Set identity gate for a coordination type
/// @param coordinationType The ERC-8001 coordination type
/// @param gatekeeperNode Agent whose trust graph gates entry
/// @param params Validation parameters for the gate
functionsetIdentityGate(bytes32coordinationType,bytes32gatekeeperNode,ValidationParamscalldataparams)external;/// @notice Remove identity gate for a coordination type
/// @param coordinationType The ERC-8001 coordination type
functionremoveIdentityGate(bytes32coordinationType)external;/// @notice Get identity gate configuration
/// @param coordinationType The ERC-8001 coordination type
/// @return gatekeeperNode The gatekeeper agent
/// @return params Validation parameters
/// @return enabled Whether the gate is active
functiongetIdentityGate(bytes32coordinationType)externalviewreturns(bytes32gatekeeperNode,ValidationParamsmemoryparams,boolenabled);/// @notice Validate participant using pre-computed path
/// @param coordinationType The ERC-8001 coordination type
/// @param path Pre-computed trust path from gatekeeper to participant
/// @return isValid Whether participant passes the gate
functionvalidateParticipantWithPath(bytes32coordinationType,TrustPathcalldatapath)externalviewreturns(boolisValid);}
OPTIONAL Interface Extensions
The following functions are OPTIONAL. Implementations MAY include them but they are not required for compliance:
interfaceITrustRegistryExtendedisITrustRegistry{/// @notice Get agents trusted by a given agent (paginated)
/// @dev OPTIONAL - useful for indexing but not required
functiongetTrustees(bytes32trustorNode,TrustLevelminLevel,bytes32scope,uint256offset,uint256limit)externalviewreturns(bytes32[]memorytrustees,uint256total);/// @notice Get agents that trust a given agent (paginated)
/// @dev OPTIONAL - useful for indexing but not required
functiongetTrustors(bytes32trusteeNode,TrustLevelminLevel,bytes32scope,uint256offset,uint256limit)externalviewreturns(bytes32[]memorytrustors,uint256total);/// @notice Validate an agent through on-chain graph traversal
/// @dev OPTIONAL - expensive, prefer off-chain computation with verifyPath
/// @param validatorNode The validating agent's perspective
/// @param targetNode The agent to validate
/// @param params Validation parameters
/// @param marginalThreshold Number of marginal attestations required (for accumulation)
/// @param fullThreshold Number of full attestations required
functionvalidateAgent(bytes32validatorNode,bytes32targetNode,ValidationParamscalldataparams,uint8marginalThreshold,uint8fullThreshold)externalviewreturns(boolisValid,uint8pathLength,uint8marginalCount,uint8fullCount);/// @notice Check if any trust path exists
/// @dev OPTIONAL - expensive, prefer off-chain computation
functionpathExists(bytes32fromNode,bytes32toNode,uint8maxDepth)externalviewreturns(boolexists,uint8depth);/// @notice Validate participant without pre-computed path
/// @dev OPTIONAL - expensive, prefer validateParticipantWithPath
functionvalidateParticipant(bytes32coordinationType,bytes32participantNode,uint8marginalThreshold,uint8fullThreshold)externalviewreturns(boolisValid);}
The signature does not verify per the Signature Authority section
The ENS name for trustorNode does not exist (owner is zero address)
If valid:
The trust record MUST be stored, keyed by (trustorNode, trusteeNode, scope)
getNonce(trustorNode) MUST return the attestation’s nonce
TrustSet MUST be emitted
setTrustBatch
setTrustBatch MUST revert if:
attestations.length != signatures.length
Any attestation has a different trustorNode than the first attestation
Any individual attestation would fail setTrust validation
Nonces within the batch MUST be strictly increasing.
revokeTrust
revokeTrust MUST revert if:
Caller is not the ENS owner or an approved operator for trustorNode
No existing trust relationship exists for (trustorNode, trusteeNode, scope) (level is Unknown)
If valid:
Trust level MUST be set to None
TrustRevoked MUST be emitted
The relationship MUST remain in storage (not deleted) to preserve the explicit distrust
verifyPath — Path Verification Algorithm
verifyPath validates a pre-computed trust path.
Algorithm:
functionverifyPath(TrustPathcalldatapath,ValidationParamscalldataparams)externalviewreturns(boolvalid,boolanchorSatisfied){// Path must have at least 2 nodes (validator and target)
if(path.nodes.length<2)return(false,false);// Path length constraint (edges = nodes - 1)
if(path.nodes.length-1>params.maxPathLength)return(false,false);// Track anchor satisfaction
boolfoundAnchor=params.requiredAnchors.length==0;// Verify each edge
for(uint256i=0;i<path.nodes.length-1;i++){// Try scoped trust first, fall back to universal
(TrustLevellevel,uint64expiry)=getTrust(path.nodes[i],path.nodes[i+1],params.scope);// Fall back to universal scope if scoped trust not found
if(level==TrustLevel.Unknown&¶ms.scope!=bytes32(0)){(level,expiry)=getTrust(path.nodes[i],path.nodes[i+1],bytes32(0));}// Edge must meet minimum trust level
if(level<params.minEdgeTrust)return(false,foundAnchor);// None explicitly voids (even if minEdgeTrust is somehow None)
if(level==TrustLevel.None)return(false,foundAnchor);// Expiry check
if(params.enforceExpiry&&expiry!=0&&expiry<=block.timestamp){return(false,foundAnchor);}// Anchor check (intermediate nodes only, not first or last)
if(!foundAnchor&&i>0){for(uint256j=0;j<params.requiredAnchors.length;j++){if(path.nodes[i]==params.requiredAnchors[j]){foundAnchor=true;break;}}}}return(true,foundAnchor);}
Scope fallback semantics:
When validating an edge, implementations MUST:
First check for trust at the specified params.scope
If not found and params.scope != bytes32(0), check for trust at universal scope bytes32(0)
Universal trust applies to all scopes
validateParticipantWithPath
This function gates ERC-8001 coordination participation.
functionvalidateParticipantWithPath(bytes32coordinationType,TrustPathcalldatapath)externalviewreturns(boolisValid){(bytes32gatekeeperNode,ValidationParamsmemoryparams,boolenabled)=getIdentityGate(coordinationType);if(!enabled)returntrue;// No gate = open participation
// Verify path starts at gatekeeper and ends at participant
if(path.nodes.length<2)returnfalse;if(path.nodes[0]!=gatekeeperNode)returnfalse;(boolvalid,boolanchorOk)=verifyPath(path,params);returnvalid&&anchorOk;}
ENS is finalised EIP-137, battle-tested, and widely adopted. Creating a new identity system would:
Add dependency on draft standards
Fragment the identity ecosystem
Require new adoption efforts
ENS provides everything needed: stable identifiers, ownership semantics, and extensibility.
Why Scope as Storage Key?
A trustor may have different trust levels for the same trustee in different contexts. For example:
Trust bob.eth fully for DeFi coordination
Trust bob.eth marginally for gaming
Making scope part of the storage key (trustorNode, trusteeNode, scope) enables this naturally. Universal trust bytes32(0) serves as a fallback when scoped trust is not specified.
Why minEdgeTrust Instead of Marginal/Full Thresholds?
The marginalThreshold and fullThreshold parameters were designed for on-chain graph traversal with marginal accumulation logic. Since on-chain traversal is OPTIONAL (expensive, DoS-prone), and the core primitive is verifyPath, we need only specify the minimum trust level each edge must have.
This simplification:
Reduces parameter complexity
Makes path verification straightforward
Leaves accumulation semantics to OPTIONAL extensions
For use cases requiring marginal accumulation, the OPTIONAL validateAgent extension accepts threshold parameters.
Why Separate Signing Authority from Transaction Submission?
ENS approvals (isApprovedForAll) are designed for operators to manage names on behalf of owners. However, allowing approved operators to forge attestation signatures would break the cryptographic binding between attestations and ENS owners.
By restricting signing authority to the ENS owner (or EIP-1271 for contract owners) while allowing operators to submit transactions like revokeTrust, we preserve:
Cryptographic integrity of attestations
Operational flexibility for name management
Clear security boundaries
Why verifyPath Only (No On-Chain Search)?
On-chain graph traversal is expensive and creates DoS vectors:
Branching factor can explode with user-controlled adjacency lists
Gas costs are unpredictable
Attackers can bloat trustee lists
By requiring pre-computed paths, this standard:
Keeps on-chain verification O(path length)
Pushes search complexity to off-chain indexers where it belongs
Enables predictable gas costs
Implementations MAY add validateAgent and pathExists as OPTIONAL extensions, but these are not required for compliance.
Why Four Trust Levels?
The four-level model (Unknown, None, Marginal, Full) is proven by GnuPG’s 25+ years of use. Finer granularity adds complexity without clear benefit; coarser granularity loses important distinctions.
With minEdgeTrust, applications can choose their security posture:
Sybil attacks are the primary threat to web of trust systems. Required anchors force trust paths to traverse established community nodes (DAOs, protocols, auditors), transforming Sybil resistance from application-layer advice into protocol-level enforcement.
Backwards Compatibility
This ERC introduces new functionality and does not modify existing standards.
ENS Compatibility: Uses standard ENS interfaces (owner, isApprovedForAll). Works with any ENS deployment. Does not rely on CCIP-Read or other off-chain mechanisms.
ERC-8001 Compatibility: Designed as a module. ERC-8001 coordinators can optionally integrate identity gates.
Wallet Compatibility: Uses EIP-712 signatures, compatible with all major wallets. Supports EIP-1271 for contract wallets and smart accounts.
An attacker can create many ENS names and establish mutual trust between them.
Protocol-level mitigations:
Required anchors: ValidationParams.requiredAnchors forces paths through established community nodes
Short path limits: maxPathLength: 2 requires close proximity to validators
High trust requirement: minEdgeTrust: Full rejects marginal trust paths
Application-level mitigations:
Weight trust by ENS name age or registration cost
Implement additional stake requirements
Monitor trust graphs for anomalous patterns off-chain
Trust Graph Manipulation
Attackers may attempt to position themselves in many trust paths.
Mitigations:
Monitor trust graphs for anomalous patterns off-chain
Use minEdgeTrust: Full for high-value coordination
Require multiple independent paths via OPTIONAL extensions
Key Compromise
If an ENS name’s controller is compromised:
Mitigations:
Agents SHOULD monitor for unexpected trust changes via TrustSet events
Use short expiries (90 days maximum recommended for high-stakes)
ENS name owners can rotate controllers
Affected agents can issue TrustRevoked to quarantine compromised nodes
ENS Name Transfer
When an ENS name is transferred:
New owner inherits trust where they are the trustee
New owner can manage trust where they are the trustor
Old attestations signed by old owner remain valid until expiry
Mitigations:
Use short expiries for high-stakes trust
Monitor ENS Transfer events
Re-evaluate trust after transfers
Replay Protection
EIP-712 domain binding prevents cross-contract replay. Monotonic nonces prevent replay within the same contract. The chainId in the domain prevents cross-chain replay.
Stale Trust
Trust relationships may become stale if agents don’t update them.
Mitigations:
Use enforceExpiry: true in validation parameters
Set reasonable expiry values on attestations (RECOMMENDED: 90 days maximum for high-stakes)
Monitor TrustSet event timestamps off-chain
Off-Chain Path Computation
This standard assumes off-chain indexers compute trust paths. Malicious indexers could: