Registry Patterns
Immutable registry architecture with code hash verification for secure infrastructure management.
Overview
The Integra system uses a unified immutable registry contract to manage critical infrastructure components across the entire platform, providing a single source of truth for component discovery and validation. This registry architecture captures and verifies contract code hashes at registration time, preventing malicious upgrades and metamorphic contract attacks while enabling graceful degradation when components become unavailable. The immutable nature of the registry itself ensures that the validation logic can never be compromised, creating ultimate trust in the infrastructure lookup system.
The IntegraRegistryV7_Immutable contract supports four distinct component types through a unified interface, eliminating the need for separate registries per component category. PROVIDER components manage attestation systems including EAS, Verifiable Credentials, ZK proofs, and DIDs. VERIFIER components handle ZK proof verification for systems like Groth16, PLONK, and Poseidon hash functions. RESOLVER components extend document functionality through lifecycle management, compliance checking, and custom business logic hooks. TOKENIZER components are registered for validation and discovery, supporting all ERC standards including ERC-721, ERC-1155, and ERC-20 implementations. Each component registration includes code hash verification, active/inactive lifecycle status, human-readable metadata, and enumeration support for off-chain discovery and monitoring.
Why an Immutable Unified Registry?
Trust Guarantees
The registry is immutable because it provides critical infrastructure that must be trustworthy:
For All Component Types:
- Code hash verification logic must be reliable
- Cannot be upgraded to bypass security checks
- Graceful degradation pattern must work correctly
- Users must trust that all infrastructure components are validated
For PROVIDER Components (Attestation Providers):
- Attestation verification must be trustworthy
- No risk of malicious provider substitution
For VERIFIER Components (ZK Verifiers):
- ZK verifier addresses must be trustworthy
- Proof verification is security-critical
- Must guarantee proof integrity forever
For RESOLVER Components (Document Resolvers):
- Resolver lookup must be reliable
- Service composition pattern depends on it
- Cannot risk DOS via compromised registry
For TOKENIZER Components (Token Implementations):
- Tokenizer validation must be consistent
- Code hash ensures tokenizer hasn’t been compromised
Benefits
- Permanent: Logic never changes after deployment
- Trustless: No governance can modify validation logic
- Gas Efficient: No proxy overhead
- Simpler: No upgrade complexity
- Predictable: Behavior guaranteed forever
Pattern 1: Code Hash Verification
Description
The core security pattern: capture contract code hash at registration, validate it hasn’t changed on every retrieval.
Registration with Code Hash Capture
/**
* @notice Register provider/verifier/resolver with code integrity tracking
* @dev Captures code hash at registration for integrity verification
*/
function registerProvider(
bytes32 providerId,
address provider,
string calldata providerType,
string calldata description
) external onlyRole(GOVERNOR_ROLE) {
if (provider == address(0)) revert ZeroAddress();
// Check for duplicate registration
if (providers[providerId].providerAddress != address(0)) {
revert ProviderAlreadyRegistered(providerId, providers[providerId].providerAddress);
}
// SECURITY: Verify provider is a contract (not EOA)
uint256 codeSize;
assembly {
codeSize := extcodesize(provider)
}
if (codeSize == 0) revert NotAContract(provider);
// SECURITY: Capture code hash for integrity verification
// Uses extcodehash opcode to get keccak256 of contract bytecode
bytes32 codeHash;
assembly {
codeHash := extcodehash(provider)
}
providers[providerId] = ProviderInfo({
providerAddress: provider,
codeHash: codeHash, // ✅ Store code hash
active: true,
registeredAt: block.timestamp,
description: description,
providerType: providerType
});
// Add to enumeration
providerIds.push(providerId);
providerIndex[providerId] = providerIds.length;
emit ProviderRegistered(
providerId,
provider,
codeHash, // ✅ Event includes code hash
providerType,
description,
block.timestamp
);
}Retrieval with Code Hash Validation
/**
* @notice Get provider address with code integrity verification
* @dev Returns address(0) if provider inactive or code changed
*
* SECURITY VALIDATION:
* 1. ✅ Checks provider exists
* 2. ✅ Checks provider is active
* 3. ✅ Validates code hash matches registration
* 4. ✅ Returns address(0) if any check fails
*/
function getProvider(bytes32 providerId) external view returns (address) {
ProviderInfo storage info = providers[providerId];
// CHECK 1: Provider exists
if (info.providerAddress == address(0)) {
return address(0); // Not registered
}
// CHECK 2: Provider is active
if (!info.active) {
return address(0); // Deactivated
}
// CHECK 3: SECURITY - Verify code hasn't changed
address providerAddr = info.providerAddress;
bytes32 currentHash;
assembly {
currentHash := extcodehash(providerAddr)
}
if (currentHash != info.codeHash) {
return address(0); // ✅ Code changed - SECURITY VIOLATION
}
return info.providerAddress; // ✅ All checks passed
}Security Benefits
Prevents Malicious Upgrades:
// Scenario: Attacker upgrades registered contract to malicious code
// 1. Contract registered: codeHash = 0xabc123...
// 2. Attacker upgrades contract (if upgradeable): codeHash = 0xdef456...
// 3. getProvider() called:
// - currentHash (0xdef456...) != registeredHash (0xabc123...)
// - Returns address(0) ✅ Attack preventedDetects SELFDESTRUCT Attacks:
// Scenario: Attacker destroys and redeploys contract at same address
// 1. Contract registered: codeHash = 0xabc123...
// 2. Attacker calls SELFDESTRUCT
// 3. Attacker deploys new malicious contract at same address
// 4. getProvider() called:
// - New contract has different bytecode
// - currentHash != registeredHash
// - Returns address(0) ✅ Attack preventedStops Metamorphic Contracts:
// Scenario: Metamorphic contract changes code via CREATE2
// 1. Metamorphic contract registered: codeHash = 0xabc123...
// 2. Contract metamorphs to new code: codeHash = 0xdef456...
// 3. getProvider() called:
// - Code hash changed
// - Returns address(0) ✅ Attack preventedPattern 2: Graceful Degradation
Description
Instead of reverting when a provider/verifier/resolver is unavailable, the registry returns address(0) and lets the caller decide how to handle it.
Why Graceful Degradation?
// ❌ BAD: Revert on unavailable provider
function getProvider(bytes32 providerId) external view returns (address) {
ProviderInfo storage info = providers[providerId];
if (info.providerAddress == address(0)) {
revert ProviderNotFound(providerId); // ❌ Reverts
}
if (!info.active) {
revert ProviderInactive(providerId); // ❌ Reverts
}
if (currentHash != info.codeHash) {
revert ProviderCodeChanged(...); // ❌ Reverts
}
return info.providerAddress;
}
// Problem: If provider becomes unavailable (code changed, deactivated, etc.),
// ALL operations using this provider REVERT, even if they could degrade gracefully.
// This creates a DOS vector!// ✅ GOOD: Return address(0) for unavailable provider
function getProvider(bytes32 providerId) external view returns (address) {
ProviderInfo storage info = providers[providerId];
if (info.providerAddress == address(0)) return address(0); // ✅ Graceful
if (!info.active) return address(0); // ✅ Graceful
if (currentHash != info.codeHash) return address(0); // ✅ Graceful
return info.providerAddress;
}
// Benefit: Caller decides how to handle unavailable provider:
// - Critical operations: Revert with custom error
// - Optional operations: Skip with event emission
// - Fallback operations: Use alternative providerCaller Handling Patterns
Pattern 1: Critical Operation (Must Revert)
// AttestationAccessControlV7.sol - Provider is required
function _verifyCapability(...) internal {
address provider = PROVIDER_REGISTRY.getProvider(providerId);
if (provider == address(0)) {
// Critical: Attestation verification cannot proceed
revert ProviderNotFound(providerId); // ✅ Revert with custom error
}
// Proceed with verification
(bool verified, uint256 capabilities) = IAttestationProvider(provider)
.verifyCapabilities(...);
}Pattern 2: Optional Operation (Emit Event, Continue)
// IntegraDocumentRegistryV7.sol - Primary resolver is best-effort
function _callPrimaryResolver(bytes32 integraHash, bytes4 selector, bytes memory data)
internal
returns (bool success)
{
DocumentRecord storage doc = documents[integraHash];
if (doc.primaryResolverId == bytes32(0)) return true; // No resolver
address resolver = resolverRegistry.getResolver(doc.primaryResolverId);
if (resolver == address(0)) {
// Optional: Emit event but don't revert
emit PrimaryResolverUnavailable(integraHash, doc.primaryResolverId);
return true; // ✅ Continue with graceful degradation
}
// Attempt to call resolver
(bool callSuccess,) = resolver.call{gas: gasLimit}(callData);
if (!callSuccess) {
revert PrimaryResolverFailed(integraHash, doc.primaryResolverId);
}
emit PrimaryResolverCalled(integraHash, doc.primaryResolverId, selector);
return true;
}Pattern 3: Fallback Operation (Use Alternative)
// Example: Use fallback provider if primary unavailable
function _verifyCapabilityWithFallback(...) internal {
address provider = PROVIDER_REGISTRY.getProvider(primaryProviderId);
if (provider == address(0)) {
// Try fallback provider
provider = PROVIDER_REGISTRY.getProvider(fallbackProviderId);
if (provider == address(0)) {
// No providers available
revert NoProvidersAvailable();
}
emit FallbackProviderUsed(fallbackProviderId);
}
// Proceed with verification using available provider
}Benefits
- Prevents DOS: One failed provider doesn’t break entire system
- Flexible Handling: Caller chooses between revert/skip/fallback
- Transparent: Events track failures
- Progressive Failure: System degrades gracefully
- User Choice: Critical vs optional operations handled differently
Pattern 3: Active/Inactive Status
Description
Providers/verifiers/resolvers can be temporarily deactivated without removal, allowing for lifecycle management.
Deactivation
/**
* @notice Deactivate provider (emergency stop)
* @dev Does not remove provider, allows reactivation
*
* USE CASES:
* - Provider bug discovered
* - Provider being upgraded
* - Temporary security concern
* - Scheduled maintenance
*/
function deactivateProvider(bytes32 providerId, string calldata reason)
external
onlyRole(GOVERNOR_ROLE)
{
ProviderInfo storage info = providers[providerId];
if (info.providerAddress == address(0)) revert ProviderNotFound(providerId);
info.active = false;
emit ProviderDeactivated(providerId, info.providerAddress, block.timestamp);
emit ProviderDeactivationReason(providerId, reason, block.timestamp);
}Reactivation with Safety Check
/**
* @notice Reactivate provider after deactivation
* @dev Validates code hasn't changed before reactivating
*
* SECURITY: Prevents reactivation if code changed
* - Cannot reactivate compromised provider
* - Must register new version if code changed
*/
function reactivateProvider(bytes32 providerId) external onlyRole(GOVERNOR_ROLE) {
ProviderInfo storage info = providers[providerId];
if (info.providerAddress == address(0)) revert ProviderNotFound(providerId);
// SECURITY: Verify code hasn't changed before reactivating
address providerAddr = info.providerAddress;
bytes32 currentHash;
assembly {
currentHash := extcodehash(providerAddr)
}
if (currentHash != info.codeHash) {
// Code changed since registration - cannot reactivate
revert ProviderCodeChanged(providerId, info.codeHash, currentHash);
}
info.active = true;
emit ProviderReactivated(providerId, info.providerAddress, block.timestamp);
}Benefits
- Temporary Control: Can disable without losing registration data
- Reversible: Can reactivate after fixing issues
- Safe: Reactivation validates code integrity
- Transparent: Deactivation reason logged
- Operational: Supports maintenance and emergency response
Pattern 4: Metadata Management
Description
Registries store human-readable metadata for discovery, monitoring, and transparency.
Metadata Structure
struct ProviderInfo {
address providerAddress; // Contract address
bytes32 codeHash; // Code integrity hash
bool active; // Active/inactive status
uint256 registeredAt; // Registration timestamp
string description; // Human-readable description
string providerType; // Type categorization
}Provider Types
Attestation Provider Types:
"EAS"- Ethereum Attestation Service"VC"- Verifiable Credentials"ZK"- Zero-Knowledge Proofs"DID"- Decentralized Identifiers"JWT"- JSON Web Tokens"MULTI"- Multiple attestation methods
Resolver Types:
"Lifecycle"- Document expiry, renewal, archival"Communication"- Contact endpoints, messaging"Compliance"- KYC, jurisdiction checks"Payment"- Payment automation, escrow"Governance"- DAO controls, voting"Multi-Purpose"- Combination of above
Verifier Types:
"Groth16"- Groth16 ZK proofs"PLONK"- PLONK proofs"Halo2"- Halo2 recursive proofs- Custom types allowed
Metadata Updates
/**
* @notice Update provider metadata (description and type)
* @dev Does not change address or code hash
*
* USE CASES:
* - Fix typos in description
* - Reclassify provider type
* - Add more details
* - Update branding
*/
function updateProviderMetadata(
bytes32 providerId,
string calldata newDescription,
string calldata newProviderType
) external onlyRole(GOVERNOR_ROLE) {
ProviderInfo storage info = providers[providerId];
if (info.providerAddress == address(0)) revert ProviderNotFound(providerId);
info.description = newDescription;
info.providerType = newProviderType;
emit ProviderMetadataUpdated(
providerId,
newDescription,
newProviderType,
block.timestamp
);
}Benefits
- Discoverability: Off-chain systems can enumerate and categorize
- Transparency: Clear descriptions of what each entry does
- Flexibility: Metadata can be updated without re-registration
- Monitoring: Easy to track provider ecosystem growth
Pattern 5: Enumeration Support
Description
Registries support enumeration for off-chain discovery and monitoring.
Implementation
/// @notice List of all provider IDs for enumeration
bytes32[] public providerIds;
/// @notice Index of provider ID in providerIds array (providerId => index + 1)
/// @dev 0 means not exists, 1 means index 0, etc.
mapping(bytes32 => uint256) private providerIndex;
/**
* @notice Get total number of registered providers
*/
function getProviderCount() external view returns (uint256) {
return providerIds.length;
}
/**
* @notice Get provider ID by index (for enumeration)
* @param index Index in providerIds array
* @return Provider ID at index
*/
function getProviderIdByIndex(uint256 index) external view returns (bytes32) {
if (index >= providerIds.length) revert IndexOutOfBounds(index);
return providerIds[index];
}
/**
* @notice Get all provider IDs
* @return Array of all provider IDs
*/
function getAllProviderIds() external view returns (bytes32[] memory) {
return providerIds;
}
/**
* @notice Get all active providers
* @return Array of active provider IDs
*/
function getActiveProviders() external view returns (bytes32[] memory) {
uint256 activeCount = 0;
// Count active providers
for (uint256 i = 0; i < providerIds.length; i++) {
if (providers[providerIds[i]].active) {
activeCount++;
}
}
// Build result array
bytes32[] memory activeIds = new bytes32[](activeCount);
uint256 index = 0;
for (uint256 i = 0; i < providerIds.length; i++) {
if (providers[providerIds[i]].active) {
activeIds[index] = providerIds[i];
index++;
}
}
return activeIds;
}Off-Chain Usage
// Enumerate all providers
const count = await providerRegistry.getProviderCount();
for (let i = 0; i < count; i++) {
const providerId = await providerRegistry.getProviderIdByIndex(i);
const info = await providerRegistry.getProviderInfo(providerId);
console.log(`Provider: ${info.description}`);
console.log(` Type: ${info.providerType}`);
console.log(` Active: ${info.active}`);
console.log(` Address: ${info.providerAddress}`);
}
// Get only active providers
const activeIds = await providerRegistry.getActiveProviders();
console.log(`${activeIds.length} active providers`);Benefits
- Discovery: Off-chain systems can find all providers
- Monitoring: Track ecosystem growth and health
- UI Support: Build provider selection interfaces
- Analytics: Analyze provider usage patterns
Testing Strategy
Code Hash Tests
describe("Code Hash Verification", () => {
it("should capture code hash at registration", async () => {
await registry.registerProvider(id, provider.address, "EAS", "Test Provider");
const info = await registry.getProviderInfo(id);
expect(info.codeHash).to.not.equal(ethers.constants.HashZero);
});
it("should detect code changes", async () => {
await registry.registerProvider(id, provider.address, "EAS", "Test");
// Upgrade provider (if upgradeable)
await provider.upgradeTo(newImplementation.address);
// getProvider should return address(0)
const retrieved = await registry.getProvider(id);
expect(retrieved).to.equal(ethers.constants.AddressZero);
});
it("should detect SELFDESTRUCT + redeploy", async () => {
await registry.registerProvider(id, provider.address, "EAS", "Test");
// SELFDESTRUCT provider
await provider.destroy();
// Deploy new contract at same address (if possible via CREATE2)
const newProvider = await deployAtAddress(provider.address);
// getProvider should return address(0) (code changed)
const retrieved = await registry.getProvider(id);
expect(retrieved).to.equal(ethers.constants.AddressZero);
});
});Graceful Degradation Tests
describe("Graceful Degradation", () => {
it("should return address(0) for non-existent provider", async () => {
const provider = await registry.getProvider(nonExistentId);
expect(provider).to.equal(ethers.constants.AddressZero);
});
it("should return address(0) for inactive provider", async () => {
await registry.registerProvider(id, provider.address, "EAS", "Test");
await registry.deactivateProvider(id, "Testing");
const retrieved = await registry.getProvider(id);
expect(retrieved).to.equal(ethers.constants.AddressZero);
});
it("should allow caller to handle unavailable provider", async () => {
// Register and deactivate
await registry.registerProvider(id, provider.address, "EAS", "Test");
await registry.deactivateProvider(id, "Testing");
// Caller handles gracefully
const provider = await registry.getProvider(id);
if (provider === ethers.constants.AddressZero) {
// Skip operation (no revert)
console.log("Provider unavailable, skipping");
}
});
});Lifecycle Tests
describe("Provider Lifecycle", () => {
it("should deactivate and reactivate provider", async () => {
await registry.registerProvider(id, provider.address, "EAS", "Test");
await registry.deactivateProvider(id, "Maintenance");
let retrieved = await registry.getProvider(id);
expect(retrieved).to.equal(ethers.constants.AddressZero);
await registry.reactivateProvider(id);
retrieved = await registry.getProvider(id);
expect(retrieved).to.equal(provider.address);
});
it("should prevent reactivation if code changed", async () => {
await registry.registerProvider(id, provider.address, "EAS", "Test");
await registry.deactivateProvider(id, "Maintenance");
// Upgrade provider while deactivated
await provider.upgradeTo(newImplementation.address);
// Reactivation should fail
await expect(
registry.reactivateProvider(id)
).to.be.revertedWithCustomError(registry, "ProviderCodeChanged");
});
});Integration Guidelines
For Governance
-
Register Providers:
await providerRegistry.registerProvider( providerId, providerAddress, "EAS", "Ethereum Attestation Service V1" ); -
Monitor Code Integrity:
// Set up monitoring providerRegistry.on("ProviderRegistered", (id, address, codeHash) => { // Store code hash for monitoring monitorCodeHash(address, codeHash); }); // Periodic validation async function validateProviders() { const ids = await providerRegistry.getAllProviderIds(); for (const id of ids) { const provider = await providerRegistry.getProvider(id); if (provider === ethers.constants.AddressZero) { alert(`Provider ${id} unavailable!`); } } } -
Handle Deactivation:
// Emergency deactivation await providerRegistry.deactivateProvider( providerId, "Security vulnerability discovered, investigating" ); // After fix, reactivate await providerRegistry.reactivateProvider(providerId);
See Also
- Security Patterns - Code hash verification security
- Resolver Patterns - Resolver composition using registries
- Foundation Documentation - Attestation provider architecture