EASAttestationProviderV7
Overview
Version: 7.0.0 Type: UUPS Upgradeable Contract License: MIT Inherits: UUPSUpgradeable, AccessControlUpgradeable, PausableUpgradeable, IAttestationProvider
EASAttestationProviderV7 is the EAS-based implementation of IAttestationProvider with comprehensive 13-step verification. It provides robust attestation verification for the Integra V7 architecture using Ethereum Attestation Service.
Purpose
- Implement IAttestationProvider interface for EAS attestations
- Perform comprehensive 13-step verification process
- Manage attestation issuers with owner override capabilities
- Prevent cross-chain replay attacks
- Validate attestation freshness and integrity
Key Features
- 13-step verification with chain context validation
- Cross-chain replay prevention through chain ID validation
- EAS contract verification to prevent spoofing
- Document contract binding for security
- Issuer authorization with owner override
- Optional attestation age limits
- Batch issuer management
- Pausable for emergency stops
Architecture
Design Philosophy
Why Upgradeable?
Unlike immutable registries, the provider is upgradeable to:
- Fix bugs in verification logic
- Optimize gas costs
- Add new verification features
- Update to new EAS versions
Security Through Layers:
Defense Layer 1: Registry code hash validation (prevents malicious provider)
Defense Layer 2: 13-step verification (comprehensive attestation validation)
Defense Layer 3: Issuer authorization (prevents unauthorized attestations)
Defense Layer 4: Chain context validation (prevents replay attacks)Integration Points
- AttestationAccessControlV7 (Foundation): Calls verifyCapabilities()
- EAS Contract: Fetches attestations
- CapabilityNamespaceV7 (Foundation): References capability definitions
- Document Contracts: Validates contract binding
Key Concepts
1. 13-Step Verification Process
Comprehensive validation ensuring attestation integrity:
1. Fetch attestation from EAS
2. Verify attestation exists
3. Verify not revoked
4. Verify not expired
5. Verify schema matches
6. Verify recipient matches (front-running protection)
7. Verify attester is authorized issuer
8. Verify source chain ID matches (replay prevention)
9. Verify source EAS contract matches (spoofing prevention)
10. Verify document contract matches (contract binding)
11. Verify schema version matches (version validation)
12. Verify document hash matches
13. Verify attestation not too old (optional age validation)Why 13 Steps?
Each step addresses a specific attack vector:
- Steps 1-5: Basic EAS attestation validation
- Step 6: Front-running protection
- Step 7: Authorization validation
- Steps 8-10: Replay and spoofing prevention
- Step 11: Schema version validation
- Step 12: Document binding
- Step 13: Freshness validation
2. EAS Schema Format
Uses INTEGRA_CAPABILITY schema with 14 fields:
struct IntegraCapabilityAttestation {
bytes32 documentHash; // Document identifier
uint256 tokenId; // Token ID (if applicable)
uint256 capabilities; // Capability bitmask
string verifiedIdentity; // Verified identity info
string verificationMethod; // How identity was verified
uint256 verificationDate; // When identity was verified
string contractRole; // Role in contract (e.g., "Buyer", "Seller")
string legalEntityType; // Entity type (e.g., "Individual", "Corporation")
string notes; // Additional notes
uint256 sourceChainId; // Chain ID (replay prevention)
address sourceEASContract; // EAS contract address (spoofing prevention)
address documentContract; // Document contract address (binding)
uint64 issuedAt; // Issuance timestamp (age validation)
bytes32 attestationVersion; // Schema version (version validation)
}3. Issuer Management
Three-level issuer hierarchy:
- Default Issuer: Set by governor for document
- Owner Override: Document owner can override default
- Revocation: Owner can revoke all issuers
Priority:
1. Check if revoked → return address(0)
2. Check owner-set issuer → return if exists
3. Return default issuerUse Cases:
- Governor sets default issuer (trusted KYC provider)
- Owner overrides for self-attestation
- Owner revokes if issuer compromised
4. Chain Context Validation
Prevents cross-chain replay attacks:
// Attestation includes:
uint256 sourceChainId; // Chain where attestation was created
address sourceEASContract; // EAS contract address
address documentContract; // Document contract address
// Verification checks:
require(sourceChainId == block.chainid, "Wrong chain");
require(sourceEASContract == address(eas), "Wrong EAS");
require(documentContract == msg.sender, "Wrong contract");Attack Prevention:
- Cannot replay attestation from another chain
- Cannot use attestation on different contract
- Cannot spoof with fake EAS contract
State Variables
Constants
string public constant VERSION = "7.0.0";
bytes32 public constant PROVIDER_TYPE = keccak256("EAS");
bytes32 public constant SCHEMA_VERSION = keccak256("INTEGRA_CAPABILITY_V7.0.0");
uint256 public constant MIN_ATTESTATION_AGE = 1 hours;
uint256 public constant MAX_ATTESTATION_AGE_LIMIT = 365 days;Roles
bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");Immutable References
IEAS public eas; // EAS contract instance
bytes32 public accessCapabilitySchema; // Capability schema UID
CapabilityNamespaceV7_Immutable public NAMESPACE; // Capability namespaceSet during initialization, treated as immutable.
Issuer State
mapping(bytes32 => address) public documentIssuers; // Default issuers
mapping(bytes32 => address) public ownerSetIssuers; // Owner overrides
mapping(bytes32 => uint256) public issuerRevokedAt; // Revocation timestampsConfiguration
uint256 public maxAttestationAge; // Optional age limit (0 = no limit)Functions
Initialization
function initialize(
address _eas,
bytes32 _accessCapabilitySchema,
address _namespace,
address _governor
) external initializerInitialize EAS Attestation Provider.
Parameters:
_eas: EAS contract address_accessCapabilitySchema: Capability schema UID_namespace: CapabilityNamespaceV7_Immutable address_governor: Governor address
Validation:
- All addresses non-zero
- Schema UID non-zero
Example:
provider.initialize(
easAddress,
schemaUID,
namespaceAddress,
governorAddress
);IAttestationProvider Implementation
verifyCapabilities
function verifyCapabilities(
bytes calldata proof,
address recipient,
bytes32 documentHash,
uint256 requiredCapability
) external view override returns (bool verified, uint256 grantedCapabilities)Verify capabilities using EAS attestation (13-step process).
Parameters:
proof: EAS attestation UID (encoded as bytes)recipient: Address that should have capabilitiesdocumentHash: Document identifierrequiredCapability: Required capability (for optimization)
Proof Format:
bytes memory proof = abi.encode(attestationUID);Returns:
verified: Whether attestation is validgrantedCapabilities: Capabilities granted in attestation
Verification Steps:
- Decode attestation UID from proof
- Run 13-step verification (see _verifyCapabilityInternal)
- Return verified status and capabilities
Example:
bytes32 attestationUID = 0x123...;
bytes memory proof = abi.encode(attestationUID);
(bool verified, uint256 caps) = provider.verifyCapabilities(
proof,
userAddress,
documentHash,
NAMESPACE.CORE_CLAIM()
);
require(verified, "Verification failed");
require(NAMESPACE.hasCapability(caps, NAMESPACE.CORE_CLAIM()), "Insufficient caps");getProviderInfo
function getProviderInfo()
external
pure
override
returns (string memory name, string memory version, bytes32 providerType)Get provider information.
Returns:
name: “EAS Attestation Provider V7”version: “7.0.0”providerType: keccak256(“EAS”)
supportsMethod
function supportsMethod(bytes32 methodId)
external
pure
override
returns (bool)Check if provider supports a method.
Parameters:
methodId: Method identifier (keccak256 of method name)
Returns: Whether method is supported
Example:
bool supported = provider.supportsMethod(keccak256("verifyCapabilities"));
// Returns: trueIssuer Management
setDocumentIssuer
function setDocumentIssuer(bytes32 documentHash, address issuer)
external
onlyRole(GOVERNOR_ROLE)Set document issuer (governor only).
Parameters:
documentHash: Hash of the documentissuer: Address authorized to issue attestations
Validation:
- Issuer is not zero address
- Document issuer not revoked by owner
Events: Emits DocumentIssuerSet
Example:
provider.setDocumentIssuer(
documentHash,
kycProviderAddress
);setDocumentIssuersBatch
function setDocumentIssuersBatch(
bytes32[] calldata documentHashes,
address[] calldata issuers
) external onlyRole(GOVERNOR_ROLE)Batch set document issuers (gas-efficient).
Parameters:
documentHashes: Array of document hashesissuers: Array of issuer addresses (must match length)
Example:
bytes32[] memory docs = new bytes32[](100);
address[] memory issuers = new address[](100);
// Fill arrays...
provider.setDocumentIssuersBatch(docs, issuers);setDocumentIssuerByOwner
function setDocumentIssuerByOwner(
bytes32 documentHash,
address issuer,
address owner
) externalAllow document owner to set issuer (overrides default).
Parameters:
documentHash: Hash of the documentissuer: Address authorized to issue attestationsowner: Address of the document owner
Authorization: Caller MUST be the document owner
Effects:
- Sets owner-specific issuer
- Clears any previous revocation
Events: Emits DocumentIssuerSet
Example:
// Owner calls from document contract
documentContract.setDocumentIssuerByOwner(
documentHash,
selfIssuerAddress,
msg.sender
);revokeDocumentIssuer
function revokeDocumentIssuer(bytes32 documentHash, address owner)
externalRevoke document issuer (owner only).
Parameters:
documentHash: Hash of the documentowner: Address of the document owner
Authorization: Caller MUST be the document owner
Effects:
- Records revocation timestamp
- Deletes both default and owner-set issuers
- All attestations from this issuer become invalid
Events: Emits DocumentIssuerRevoked
Example:
// Owner revokes compromised issuer
provider.revokeDocumentIssuer(documentHash, msg.sender);restoreDocumentIssuer
function restoreDocumentIssuer(
bytes32 documentHash,
address issuer,
address owner
) externalRestore document issuer after revocation.
Parameters:
documentHash: Hash of the documentissuer: Address authorized to issue attestationsowner: Address of the document owner
Authorization: Caller MUST be the document owner Precondition: Issuer must have been revoked
Effects:
- Clears revocation
- Sets new owner-specific issuer
Events: Emits DocumentIssuerSet
Example:
// After resolving security issue
provider.restoreDocumentIssuer(
documentHash,
trustedIssuerAddress,
msg.sender
);Configuration
setMaxAttestationAge
function setMaxAttestationAge(uint256 newMaxAge)
external
onlyRole(GOVERNOR_ROLE)Set maximum attestation age.
Parameters:
newMaxAge: Maximum age in seconds (0 = no limit)
Validation:
- If non-zero, must be >= MIN_ATTESTATION_AGE (1 hour)
- If non-zero, must be <= MAX_ATTESTATION_AGE_LIMIT (365 days)
Events: Emits MaxAttestationAgeUpdated
Example:
// Require attestations less than 30 days old
provider.setMaxAttestationAge(30 days);
// Remove age limit
provider.setMaxAttestationAge(0);View Functions
getDocumentIssuer
function getDocumentIssuer(bytes32 documentHash)
external
view
returns (address)Get active issuer for document.
Returns: Active issuer address (or address(0) if revoked/not set)
getActiveIssuer
function getActiveIssuer(bytes32 documentHash)
external
view
returns (address issuer, bool isOwnerSet, bool isRevoked)Get detailed issuer information.
Returns:
issuer: Active issuer addressisOwnerSet: Whether issuer is owner-set (vs default)isRevoked: Whether issuer has been revoked
Example:
(address issuer, bool ownerSet, bool revoked) = provider.getActiveIssuer(documentHash);
if (revoked) {
console.log("Issuer revoked by owner");
} else if (ownerSet) {
console.log("Using owner-set issuer:", issuer);
} else {
console.log("Using default issuer:", issuer);
}getCurrentChainId
function getCurrentChainId() external view returns (uint256)Get current chain ID.
Returns: block.chainid
getEASAddress
function getEASAddress() external view returns (address)Get EAS contract address.
Returns: address(eas)
Emergency Functions
pause / unpause
function pause() external onlyRole(GOVERNOR_ROLE)
function unpause() external onlyRole(GOVERNOR_ROLE)Pause/unpause provider (emergency stop).
Effects:
- When paused, verifyCapabilities() reverts
- All capability verification stops
Use Cases:
- Critical bug discovered
- EAS contract compromised
- Emergency security response
Events
DocumentIssuerSet
event DocumentIssuerSet(
bytes32 indexed documentHash,
address indexed issuer,
address indexed setBy,
bool isOwnerOverride,
uint256 timestamp
)Emitted when document issuer is set.
DocumentIssuerRevoked
event DocumentIssuerRevoked(
bytes32 indexed documentHash,
address indexed previousIssuer,
address indexed revokedBy,
uint256 timestamp
)Emitted when document issuer is revoked.
MaxAttestationAgeUpdated
event MaxAttestationAgeUpdated(
uint256 oldAge,
uint256 newAge,
uint256 timestamp
)Emitted when max attestation age is updated.
Security Considerations
13-Step Verification Security
Each step prevents specific attacks:
| Step | Attack Prevention |
|---|---|
| 1-2 | Non-existent attestations |
| 3 | Revoked attestations |
| 4 | Expired attestations |
| 5 | Wrong schema (data format mismatch) |
| 6 | Front-running (attestation theft) |
| 7 | Unauthorized issuers |
| 8 | Cross-chain replay attacks |
| 9 | EAS contract spoofing |
| 10 | Contract spoofing/misuse |
| 11 | Schema version mismatch |
| 12 | Document mismatching |
| 13 | Stale attestations |
Issuer Security
Authorization Model:
1. Governor sets default issuer (trusted KYC provider)
2. Owner can override (self-attestation)
3. Owner can revoke (emergency response)Attack Scenarios:
Scenario 1: Compromised Default Issuer
- Owner calls revokeDocumentIssuer()
- All attestations from issuer become invalid
- Owner sets new trusted issuer
Scenario 2: Malicious Owner Override
- Owner overrides with malicious issuer
- Only affects their documents
- Governor can still manage default issuer
Best Practices:
- Vet default issuers carefully
- Monitor issuer changes via events
- Have issuer rotation plan
- Support multiple issuers for redundancy
Cross-Chain Replay Prevention
Chain Context Validation:
// Attestation created on Chain A (e.g., Ethereum)
sourceChainId = 1
sourceEASContract = 0xEAS_Ethereum
documentContract = 0xDoc_Ethereum
// Attack: Try to replay on Chain B (e.g., Polygon)
// Verification will fail:
require(sourceChainId == block.chainid) // 1 != 137 (Polygon)Additional Protection:
- EAS contract address differs per chain
- Document contract address differs per chain
- All three must match for verification to succeed
Attestation Freshness
Age Limit Configuration:
// No age limit (default)
maxAttestationAge = 0
// 30-day limit
maxAttestationAge = 30 days
// Verification:
if (maxAttestationAge > 0) {
require(block.timestamp - issuedAt <= maxAttestationAge)
}Use Cases:
- KYC attestations (require recent verification)
- Temporary permissions (expire after period)
- Security-sensitive operations (require fresh attestations)
Trade-offs:
- Stricter age limits → more secure but higher maintenance
- No age limits → convenient but potentially stale data
Usage Examples
Basic Verification
// 1. User gets EAS attestation off-chain
// 2. User calls function with attestation UID
function claimToken(bytes32 documentHash, bytes32 easUID)
external
requiresCapabilityWithUID(documentHash, NAMESPACE.CORE_CLAIM(), easUID)
nonReentrant
{
// attestationProof is automatically encoded as abi.encode(easUID)
// Provider verifies:
// - Attestation exists and valid
// - User is recipient
// - Capabilities are sufficient
// - All 13 steps pass
_mint(msg.sender, tokenId);
}Issuer Management Flow
// 1. Governor sets default issuer (KYC provider)
provider.setDocumentIssuer(documentHash, kycProviderAddress);
// 2. KYC provider creates attestation for user
// (Off-chain process)
// 3. User uses attestation
contract.claimToken(documentHash, easUID);
// 4. Later: Owner wants self-attestation
provider.setDocumentIssuerByOwner(documentHash, ownerAddress, ownerAddress);
// 5. Emergency: Issuer compromised
provider.revokeDocumentIssuer(documentHash, ownerAddress);
// 6. Recovery: Set new issuer
provider.restoreDocumentIssuer(documentHash, newIssuerAddress, ownerAddress);Batch Issuer Setup
// Setup issuers for 100 documents efficiently
bytes32[] memory docs = new bytes32[](100);
address[] memory issuers = new address[](100);
for (uint i = 0; i < 100; i++) {
docs[i] = documentHashes[i];
issuers[i] = kycProviderAddress;
}
provider.setDocumentIssuersBatch(docs, issuers);
// Gas savings: ~50% vs individual callsAge-Limited Attestations
// Require KYC attestations less than 90 days old
provider.setMaxAttestationAge(90 days);
// User's attestation
issuedAt = attestation.issuedAt; // Timestamp when attestation was created
age = block.timestamp - issuedAt;
// Verification will fail if:
if (age > 90 days) {
revert AttestationTooOld(issuedAt, 90 days);
}
// User must get new attestation from issuerIntegration Guide
Deployment
// 1. Deploy EAS (or use existing)
EAS eas = EAS(easAddress);
// 2. Register schema
SchemaRegistry registry = SchemaRegistry(schemaRegistryAddress);
bytes32 schemaUID = registry.register(SCHEMA_DEFINITION, resolver, true);
// 3. Deploy provider
EASAttestationProviderV7 provider = new EASAttestationProviderV7();
// 4. Initialize provider
provider.initialize(
address(eas),
schemaUID,
namespaceAddress,
governorAddress
);
// 5. Register in provider registry
providerRegistry.registerProvider(
keccak256("EAS_V1"),
address(provider),
"EAS",
"EAS Attestation Provider V1"
);Creating Attestations
// Off-chain: Create attestation
const attestation = {
schema: schemaUID,
data: {
recipient: userAddress,
expirationTime: 0, // No expiration
revocable: true,
refUID: ethers.constants.HashZero,
data: ethers.utils.defaultAbiCoder.encode(
[
"bytes32", "uint256", "uint256", "string", "string",
"uint256", "string", "string", "string", "uint256",
"address", "address", "uint64", "bytes32"
],
[
documentHash,
tokenId,
capabilities,
"John Doe",
"Government ID",
Date.now(),
"Buyer",
"Individual",
"KYC verified",
chainId,
easAddress,
documentContractAddress,
Math.floor(Date.now(./ 1000),
schemaVersion
]
)
}
};
// Issuer creates attestation
const tx = await eas.connect(issuer).attest(attestation);
const receipt = await tx.wait();
const easUID = receipt.events[0].args.uid;
// User can now use easUID in contract callsTesting
const provider = await ethers.getContractAt("EASAttestationProviderV7", providerAddress);
// Set issuer
await provider.connect(governor).setDocumentIssuer(documentHash, issuer.address);
// Create test attestation
const easUID = await createTestAttestation(documentHash, user.address, capabilities);
// Verify capabilities
const proof = ethers.utils.defaultAbiCoder.encode(["bytes32"], [easUID]);
const [verified, grantedCaps] = await provider.verifyCapabilities(
proof,
user.address,
documentHash,
requiredCapability
);
expect(verified).to.be.true;
expect(grantedCaps).to.equal(capabilities);