Security Patterns
Security Patterns
Section titled “Security Patterns”Comprehensive security patterns implemented across all Integra smart contracts.
Overview
Section titled “Overview”The Integra smart contract system implements defense-in-depth security with multiple independent layers of protection that work together to prevent exploits and maintain system integrity even under attack. This comprehensive security architecture combines battle-tested patterns from OpenZeppelin with custom protections designed specifically for real-world contract tokenization, creating a robust defense against the full spectrum of smart contract vulnerabilities including reentrancy, access control bypass, front-running, and denial-of-service attacks.
Each security layer operates independently to provide redundant protection, ensuring that the failure of any single mechanism does not compromise the entire system. All state-changing functions use reentrancy guards to prevent recursive call attacks, while multi-layer access control combines role-based permissions, attestation-based capabilities, and document-level authorization. The pausability pattern enables emergency shutdowns across all contracts, and the checks-effects-interactions model ensures state updates occur before external calls. Code hash verification in the registry prevents malicious contract upgrades, front-running protection validates attestation recipients, and graceful degradation returns safe default values instead of reverting to prevent denial-of-service. Finally, time-limited emergency controls with progressive expiry provide rapid incident response capability while ensuring eventual complete decentralization.
Pattern 1: Reentrancy Protection
Section titled “Pattern 1: Reentrancy Protection”Description
Section titled “Description”All state-changing functions use OpenZeppelin’s ReentrancyGuard to prevent reentrancy attacks. The nonReentrant modifier ensures no function can be called recursively, protecting against malicious contracts attempting to re-enter during external calls.
Implementation
Section titled “Implementation”// All contracts inherit ReentrancyGuardimport "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
abstract contract AttestationAccessControl is UUPSUpgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable, // ✅ Reentrancy protection PausableUpgradeable{ // Standard pattern for all state-changing functions function _verifyCapability( address user, bytes32 documentHash, uint256 requiredCapability, bytes calldata attestationProof ) internal nonReentrant whenNotPaused { // ✅ nonReentrant modifier // ... state changes
// SECURITY: External calls to attestation providers (bool verified, uint256 grantedCapabilities) = IAttestationProvider(provider) .verifyCapabilities(attestationProof, user, documentHash, requiredCapability);
// ... validation }}Benefits
Section titled “Benefits”- Complete Protection: Prevents all reentrancy attack vectors
- Zero Trust: No assumptions about external contract behavior
- Gas Efficient: Minimal overhead (~2,600 gas)
- Battle Tested: OpenZeppelin’s proven implementation
Contracts Using Pattern
Section titled “Contracts Using Pattern”AttestationAccessControl(Foundation) - All verification functionsIntegraDocumentRegistry(Document Management) - Registration, transfers, executor authorizationBaseTokenizer(Tokenization) - Token reserve, claim, cancel operationsIntegraMessage(Communication) - Message sendingIntegraSignal(Communication) - Payment request operationsIntegraExecutor(Execution) - Meta-transaction execution
Testing Strategy
Section titled “Testing Strategy”-
Reentrancy Attack Tests:
- Deploy malicious contract that attempts reentrancy
- Verify all state-changing functions revert with “ReentrancyGuard: reentrant call”
- Test both direct and cross-contract reentrancy
-
Gas Cost Analysis:
- Measure gas overhead of
nonReentrantmodifier - Verify acceptable performance impact
- Measure gas overhead of
Pattern 2: Checks-Effects-Interactions
Section titled “Pattern 2: Checks-Effects-Interactions”Description
Section titled “Description”The Checks-Effects-Interactions pattern ensures state changes occur before external calls, preventing reentrancy and race conditions. Even with nonReentrant protection, this pattern provides defense-in-depth.
Implementation
Section titled “Implementation”// TrustGraphIntegration.sol - Example from trust credential issuancefunction _issueCredentialsToAllParties(bytes32 integraHash) internal { // CHECKS: Validate preconditions if (credentialsIssued[integraHash]) { return; // Already issued }
// EFFECTS: Update state BEFORE external calls credentialsIssued[integraHash] = true; // ✅ State update first
// INTERACTIONS: External calls last (wrapped in try/catch) address[] memory parties = _getDocumentParties(integraHash);
for (uint256 i = 0; i < parties.length;) { // Try/catch prevents one failure from blocking others try this._issueCredentialToParty(parties[i], integraHash) { emit TrustCredentialIssued(integraHash, parties[i]); } catch Error(string memory reason) { emit TrustCredentialFailed(integraHash, parties[i], reason); } catch { emit TrustCredentialFailed(integraHash, parties[i], "Unknown error"); }
unchecked { ++i; } }}Benefits
Section titled “Benefits”- Reentrancy Safe: State updated before external calls
- Non-blocking: Try/catch prevents cascade failures
- Transparent: Events track both success and failure
- Idempotent: Double-issuance prevented by state flag
Critical Locations
Section titled “Critical Locations”- Trust Credential Issuance (
TrustGraphIntegration.sol:182-198) - Resolver Calls (
IntegraDocumentRegistry.sol:1004-1071) - Fee Collection (
IntegraDocumentRegistry.sol)
Pattern 3: Access Control Layers
Section titled “Pattern 3: Access Control Layers”Description
Section titled “Description”Multi-layer access control with three security levels:
- Foundation: Attestation-based capabilities (fine-grained permissions)
- Document Management: Document ownership (coarse-grained permissions)
- Tokenization: Per-document executor authorization (delegated permissions)
Foundation: Attestation-Based Access Control
Section titled “Foundation: Attestation-Based Access Control”/** * @notice Verify caller has required capability via attestation * @dev 13-step verification process with front-running protection */modifier requiresCapability( bytes32 documentHash, uint256 requiredCapability, bytes calldata attestationProof) { _verifyCapability(msg.sender, documentHash, requiredCapability, attestationProof); _;}
// 13-Step Verification (EASAttestationProvider.sol:279-402)function verifyCapabilities(...) external view returns (bool, uint256) { // 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 (cross-chain replay prevention) // 9. ✅ Verify source EAS contract (EAS spoofing prevention) // 10. ✅ Verify document contract (contract spoofing prevention) // 11. ✅ Verify schema version // 12. ✅ Verify document hash matches // 13. ✅ Verify attestation age (optional time limits)}Document Management: Document Ownership
Section titled “Document Management: Document Ownership”// IntegraDocumentRegistry.sol - Three access pathsfunction getDocumentOwner(bytes32 integraHash) public view returns (address) { DocumentRecord storage doc = documents[integraHash]; if (!doc.exists) revert DocumentNotRegistered(integraHash); return doc.owner;}
// Owner validation in critical operationsfunction transferDocumentOwnership( bytes32 integraHash, address newOwner, string calldata reason) external nonReentrant whenNotPaused { DocumentRecord storage doc = documents[integraHash];
// Only current owner can transfer if (msg.sender != doc.owner) { revert Unauthorized(msg.sender, integraHash); }
// ... transfer logic}Tokenization: Per-Document Executor Authorization
Section titled “Tokenization: Per-Document Executor Authorization”// BaseTokenizer.sol - Zero-trust executor modelmodifier requireOwnerOrExecutor(bytes32 integraHash) { // VALIDATION: Ensure document uses THIS tokenizer address documentTokenizer = documentRegistry.getTokenizer(integraHash); if (documentTokenizer == address(0)) { revert TokenizerNotSet(integraHash); } if (documentTokenizer != address(this)) { revert WrongTokenizer(integraHash, documentTokenizer, address(this)); }
// PATH 1: Document owner (highest priority) address owner = documentRegistry.getDocumentOwner(integraHash); if (msg.sender == owner) { _; return; }
// PATH 2: Per-document authorized executor (opt-in) address authorizedExecutor = documentRegistry.getDocumentExecutor(integraHash); if (authorizedExecutor != address(0) && msg.sender == authorizedExecutor) { _; return; }
// PATH 3: Unauthorized - revert revert Unauthorized(msg.sender, integraHash);}Security Benefits
Section titled “Security Benefits”- Defense in Depth: Multiple independent validation layers
- Zero Trust: No global privileges, all access explicit
- Front-Running Protection: Recipient validation in attestations
- Owner Sovereignty: Document owner always maintains control
- Flexible Delegation: Opt-in executor authorization
Testing Strategy
Section titled “Testing Strategy”-
Attestation Tests:
- Test all 13 verification steps individually
- Test front-running scenarios (wrong recipient)
- Test expired/revoked attestations
- Test cross-chain replay attacks
-
Ownership Tests:
- Test ownership transfer scenarios
- Test unauthorized access attempts
- Test edge cases (zero address, self-transfer)
-
Executor Tests:
- Test opt-in authorization
- Test revocation
- Test wrong tokenizer attacks
- Test EOA vs contract executors
Pattern 4: Pausability
Section titled “Pattern 4: Pausability”Description
Section titled “Description”Emergency circuit breaker pattern using OpenZeppelin’s Pausable. All state-changing functions can be paused by governance in case of security issues.
Implementation
Section titled “Implementation”import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
abstract contract AttestationAccessControl is UUPSUpgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable // ✅ Pausability{ // Pause functions (governance only) function pause() external onlyRole(GOVERNOR_ROLE) { _pause(); }
function unpause() external onlyRole(GOVERNOR_ROLE) { _unpause(); }
// All state-changing functions use whenNotPaused function criticalFunction(...) external nonReentrant whenNotPaused // ✅ Can be paused onlyRole(GOVERNOR_ROLE) { // ... implementation }}Benefits
Section titled “Benefits”- Emergency Stop: Immediately halt all operations
- Incident Response: Time to investigate and fix issues
- Reversible: Can be unpaused after resolution
- Governance Controlled: Only authorized roles can pause
Contracts with Pausability
Section titled “Contracts with Pausability”All contracts support pausability:
- Foundation:
AttestationAccessControl,EASAttestationProvider - Document management:
IntegraDocumentRegistry_Immutable - Tokenization: All tokenizers (via
BaseTokenizer) - Communication:
IntegraMessage,IntegraSignal - Execution:
IntegraExecutor
Pattern 5: Code Hash Verification
Section titled “Pattern 5: Code Hash Verification”Description
Section titled “Description”Registry pattern that captures and validates contract code hashes, preventing malicious contract upgrades and metamorphic contract attacks.
Implementation
Section titled “Implementation”function registerProvider( bytes32 providerId, address provider, string calldata providerType, string calldata description) external onlyRole(GOVERNOR_ROLE) { // Verify provider is a contract uint256 codeSize; assembly { codeSize := extcodesize(provider) } if (codeSize == 0) revert NotAContract(provider);
// SECURITY: Capture code hash at registration 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 });}
// Retrieval with integrity verificationfunction getProvider(bytes32 providerId) external view returns (address) { ProviderInfo storage info = providers[providerId];
// Check provider exists and is active if (info.providerAddress == address(0)) return address(0); if (!info.active) return address(0);
// 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 - graceful degradation }
return info.providerAddress;}Security Benefits
Section titled “Security Benefits”- Prevents Malicious Upgrades: Detects contract code changes
- Blocks SELFDESTRUCT Attacks: Detects destroy + redeploy
- Stops Metamorphic Contracts: Code hash validation catches metamorphism
- Graceful Degradation: Returns
address(0)instead of reverting (prevents DOS)
Registries Using Pattern
Section titled “Registries Using Pattern”AttestationProviderRegistry_Immutable- Attestation system providersIntegraVerifierRegistry_Immutable- ZK proof verifiersIntegraResolverRegistry_Immutable- Document resolvers
Testing Strategy
Section titled “Testing Strategy”-
Code Change Detection:
- Register contract with initial code
- Upgrade contract (if upgradeable)
- Verify
getProvider()returnsaddress(0)
-
SELFDESTRUCT Attack:
- Register contract
- SELFDESTRUCT contract
- Deploy new contract at same address
- Verify code hash mismatch detected
-
Graceful Degradation:
- Test calling contracts handle
address(0)properly - Verify events emitted for unavailable providers
- Test fallback logic
- Test calling contracts handle
Pattern 6: Front-Running Protection
Section titled “Pattern 6: Front-Running Protection”Description
Section titled “Description”Attestation-based access control includes recipient validation to prevent front-running attacks where an attacker intercepts and uses someone else’s attestation proof.
Implementation
Section titled “Implementation”// EASAttestationProvider.sol - Step 6 of 13-step verificationfunction verifyCapabilities( bytes calldata proof, address recipient, bytes32 documentHash, uint256 requiredCapability) external view returns (bool verified, uint256 grantedCapabilities) { // ... previous steps
// STEP 6: FRONT-RUNNING PROTECTION // Verify attestation recipient matches the caller if (attestation.recipient != recipient) { return (false, 0); // Wrong recipient - proof stolen or front-run }
// ... remaining steps}Attack Scenario Prevented
Section titled “Attack Scenario Prevented”// WITHOUT recipient validation:// 1. Alice generates attestation for herself// 2. Bob observes Alice's transaction in mempool// 3. Bob copies Alice's attestation proof// 4. Bob front-runs with higher gas, using Alice's proof// 5. Bob gains Alice's capabilities ❌
// WITH recipient validation:// 1. Alice generates attestation for herself (recipient = Alice)// 2. Bob observes Alice's transaction// 3. Bob copies Alice's proof// 4. Bob front-runs with higher gas// 5. Verification fails: attestation.recipient (Alice) != Bob ✅Benefits
Section titled “Benefits”- Prevents Proof Theft: Attestations bound to specific recipient
- Mempool Safety: Safe to broadcast transactions with proofs
- No Replay Attacks: Proofs can’t be reused by different addresses
Pattern 7: Graceful Degradation
Section titled “Pattern 7: Graceful Degradation”Description
Section titled “Description”Instead of reverting when external dependencies fail, the system returns safe default values (address(0), false) and allows callers to decide how to handle failures.
Implementation
Section titled “Implementation”// Registry returns address(0) instead of revertingfunction getProvider(bytes32 providerId) external view returns (address) { ProviderInfo storage info = providers[providerId];
if (info.providerAddress == address(0)) return address(0); // Not found if (!info.active) return address(0); // Inactive if (currentHash != info.codeHash) return address(0); // Code changed
return info.providerAddress;}
// Caller decides how to handlefunction _verifyCapability(...) internal { address provider = PROVIDER_REGISTRY.getProvider(providerId);
if (provider == address(0)) { // Option 1: Revert (critical operation) revert ProviderNotFound(providerId);
// Option 2: Emit event and skip (optional operation) emit ProviderUnavailable(providerId); return;
// Option 3: Use fallback provider provider = fallbackProvider; }}Benefits
Section titled “Benefits”- Prevents DOS: One failed dependency doesn’t break entire system
- Flexible Handling: Callers choose between revert/skip/fallback
- Transparent: Events emitted for failures
- Progressive Failure: System degrades gracefully instead of hard failure
Pattern 8: Emergency Controls
Section titled “Pattern 8: Emergency Controls”Description
Section titled “Description”Time-limited emergency powers with progressive expiry, allowing rapid response while ensuring decentralization.
Implementation
Section titled “Implementation”address public immutable emergencyAddress; // IMMUTABLEuint256 public immutable emergencyExpiry; // Set at deployment
constructor(..., address _emergencyAddress) { emergencyAddress = _emergencyAddress; emergencyExpiry = block.timestamp + 180 days; // 6 months}
function emergencyUnlockResolvers( bytes32 integraHash, string calldata justification) external nonReentrant { // TIME-GATED AUTHORIZATION if (block.timestamp < emergencyExpiry) { // First 6 months: Emergency address OR governance if (msg.sender != emergencyAddress && !hasRole(GOVERNOR_ROLE, msg.sender)) { revert UnauthorizedEmergencyUnlock(msg.sender); } } else { // After 6 months: Only governance if (!hasRole(GOVERNOR_ROLE, msg.sender)) { revert EmergencyPowersExpired(); } }
doc.resolversLocked = false;
emit ResolversEmergencyUnlocked( integraHash, msg.sender, justification, // ✅ Transparent justification block.timestamp, msg.sender == emergencyAddress, block.timestamp < emergencyExpiry );}Security Features
Section titled “Security Features”- Time-Limited: Emergency powers expire after 6 months
- Immutable Address: Cannot be changed after deployment
- Dual Authorization: Emergency OR governance during active period
- Transparent: Requires justification string in event
- Progressive Decentralization: Auto-expires to governance-only
- Trackable: Events include all context for monitoring
Recommended Configuration
Section titled “Recommended Configuration”- Emergency Address: Use Gnosis Safe multisig (3-of-5 or 5-of-9)
- Monitoring: Alert on all
ResolversEmergencyUnlockedevents - Public Reporting: Monthly transparency reports of emergency usage
- Documentation: Clear procedures for emergency scenarios
Testing Strategy
Section titled “Testing Strategy”Security Test Suite
Section titled “Security Test Suite”-
Reentrancy Tests:
describe("Reentrancy Protection", () => {it("should prevent reentrancy on verifyCapability", async () => {const maliciousProvider = await deployMaliciousProvider();// Attempt reentrancy attackawait expect(contract.verifyCapability(..., maliciousProvider)).to.be.revertedWith("ReentrancyGuard: reentrant call");});}); -
Access Control Tests:
describe("Multi-Layer Access Control", () => {it("should enforce attestation capabilities", async () => {// Test without valid attestationawait expect(tokenizer.claimToken(integraHash, invalidProof)).to.be.revertedWith("NoCapability");});it("should enforce document ownership", async () => {// Test non-owner attempting transferawait expect(registry.connect(attacker).transferOwnership(integraHash, newOwner)).to.be.revertedWith("Unauthorized");});}); -
Pausability Tests:
describe("Emergency Controls", () => {it("should pause all operations", async () => {await contract.pause();await expect(contract.registerDocument(...)).to.be.revertedWith("Pausable: paused");});}); -
Code Hash Tests:
describe("Code Integrity", () => {it("should detect code changes", async () => {await registry.registerProvider(id, provider, ...);await provider.upgrade(newImplementation);const retrieved = await registry.getProvider(id);expect(retrieved).to.equal(ethers.ZeroAddress);});});
Integration Guidelines
Section titled “Integration Guidelines”For Integrators
Section titled “For Integrators”-
Always Use Modifiers:
function yourFunction(...)externalnonReentrant // ✅ Always includewhenNotPaused // ✅ Always includerequireOwnerOrExecutor(integraHash) // ✅ Access control{// ... implementation} -
Handle Graceful Degradation:
address provider = PROVIDER_REGISTRY.getProvider(providerId);if (provider == address(0)) {emit ProviderUnavailable(providerId);return; // Or use fallback logic} -
Monitor Emergency Events:
contract.on("ResolversEmergencyUnlocked", (hash, unlocker, justification) => {logAlert(`Emergency unlock: ${justification}`);});
References
Section titled “References”- OpenZeppelin Security: https://docs.openzeppelin.com/contracts/4.x/api/security
- Ethereum Attestation Service: https://docs.attest.sh/
- Smart Contract Security Best Practices: https://consensys.github.io/smart-contract-best-practices/
See Also
Section titled “See Also”- Access Control Patterns - Detailed access control architecture
- Registry Patterns - Code hash verification deep dive
- Emergency Controls - Operational procedures