Overview
The ZKScore Identity SBT contract implements comprehensive security measures to protect against common attack vectors and ensure the integrity of the soulbound token system. This document outlines the security features, audit results, and best practices for secure integration.
Always follow security best practices when integrating with smart contracts. Never trust user input without validation and always implement proper error handling.
Security Features
Access Control
The contract implements role-based access control using OpenZeppelin’s AccessControl library:
// Role definitions
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant METADATA_ROLE = keccak256("METADATA_ROLE");
// Access control modifiers
modifier onlyRole(bytes32 role) {
require(hasRole(role, msg.sender), "AccessControl: account is missing role");
_;
}
Security Benefits:
- Granular permission control
- Prevents unauthorized minting
- Protects administrative functions
- Enables role delegation
Reentrancy Protection
All external calls are protected against reentrancy attacks:
// ReentrancyGuard from OpenZeppelin
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract IdentitySBT is ReentrancyGuard {
function mint(address to, string memory name, string memory metadataURI)
external
onlyRole(MINTER_ROLE)
nonReentrant
returns (uint256) {
// Implementation
}
}
Security Benefits:
- Prevents reentrancy attacks
- Protects against recursive calls
- Ensures state consistency
- Maintains gas efficiency
All inputs are validated before processing:
function mint(address to, string memory name, string memory metadataURI)
external
onlyRole(MINTER_ROLE)
nonReentrant
returns (uint256) {
// Input validation
require(to != address(0), "Cannot mint to zero address");
require(bytes(name).length > 0, "Name cannot be empty");
require(bytes(name).length <= 63, "Name too long");
require(bytes(metadataURI).length > 0, "Metadata URI cannot be empty");
// Check for duplicate names
require(!_nameExists[name], "Name already exists");
// Implementation
}
Security Benefits:
- Prevents invalid inputs
- Protects against overflow attacks
- Ensures data integrity
- Improves user experience
Soulbound Token Protection
The contract implements soulbound token mechanics to prevent transfers after activation:
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal override {
// Prevent transfers of activated tokens
require(!isActivated(tokenId), "Token is soulbound");
super._beforeTokenTransfer(from, to, tokenId);
}
function approve(address to, uint256 tokenId) public override {
require(!isActivated(tokenId), "Token is soulbound");
super.approve(to, tokenId);
}
Security Benefits:
- Prevents unauthorized transfers
- Maintains token ownership integrity
- Protects against social engineering
- Ensures soulbound nature
Audit Results
Security Audit by ConsenSys Diligence
Audit Date: January 2024
Audit Firm: ConsenSys Diligence
Severity: No critical or high-severity issues found
Audit Summary
The audit covered:
- Smart contract security analysis
- Access control mechanisms
- Reentrancy protection
- Input validation
- Gas optimization
- Upgrade safety
Key Findings
- No Critical Issues: No critical vulnerabilities found
- No High Severity Issues: No high-severity vulnerabilities found
- Minor Recommendations: Several minor recommendations for improvement
- Best Practices: Contract follows security best practices
Recommendations Implemented
- Enhanced Input Validation: Added comprehensive input validation
- Gas Optimization: Optimized gas usage for better efficiency
- Event Logging: Enhanced event logging for better monitoring
- Error Messages: Improved error messages for better debugging
Audit Report
The complete audit report is available at:
ConsenSys Diligence Audit Report
Known Limitations
Technical Limitations
- Metadata Immutability: Once activated, metadata cannot be changed
- Transfer Irreversibility: Once activated, tokens cannot be transferred
- Gas Costs: Complex operations may have higher gas costs
- Network Dependency: Contract behavior depends on network state
Security Considerations
- Private Key Security: Users must secure their private keys
- Metadata Security: Metadata URIs should be secure and accessible
- Role Management: Admin roles should be carefully managed
- Upgrade Safety: Core logic is immutable, only parameters are configurable
Security Best Practices
For Developers
// Always validate inputs before sending transactions
function validateMintInputs(to, name, metadataURI) {
if (!to || to === '0x0000000000000000000000000000000000000000') {
throw new Error('Invalid recipient address');
}
if (!name || name.length === 0) {
throw new Error('Name cannot be empty');
}
if (name.length > 63) {
throw new Error('Name too long');
}
if (!metadataURI || metadataURI.length === 0) {
throw new Error('Metadata URI cannot be empty');
}
// Validate URI format
try {
new URL(metadataURI);
} catch (error) {
throw new Error('Invalid metadata URI format');
}
}
Error Handling
// Implement comprehensive error handling
async function safeMint(to, name, metadataURI) {
try {
// Validate inputs
validateMintInputs(to, name, metadataURI);
// Estimate gas
const gasEstimate = await contract.estimateGas.mint(to, name, metadataURI);
// Send transaction with proper gas limit
const tx = await contract.mint(to, name, metadataURI, {
gasLimit: gasEstimate.mul(120).div(100) // 20% buffer
});
const receipt = await tx.wait();
return receipt;
} catch (error) {
// Handle specific error types
if (error.message.includes('AccessControl')) {
throw new Error('Insufficient permissions to mint');
} else if (error.message.includes('name already exists')) {
throw new Error('Name is already taken');
} else if (error.message.includes('gas limit exceeded')) {
throw new Error('Transaction gas limit exceeded');
} else {
throw new Error(`Minting failed: ${error.message}`);
}
}
}
Event Monitoring
// Monitor events for security purposes
class SecurityMonitor {
constructor(contract) {
this.contract = contract;
this.setupEventMonitoring();
}
setupEventMonitoring() {
// Monitor for suspicious activity
this.contract.on('IdentityMinted', (to, tokenId, name, event) => {
this.logMintEvent(to, tokenId, name, event);
});
this.contract.on('IdentityActivated', (tokenId, owner, event) => {
this.logActivationEvent(tokenId, owner, event);
});
}
logMintEvent(to, tokenId, name, event) {
console.log(`Security: Identity minted`, {
to,
tokenId: tokenId.toString(),
name,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash
});
}
logActivationEvent(tokenId, owner, event) {
console.log(`Security: Identity activated`, {
tokenId: tokenId.toString(),
owner,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash
});
}
}
For Users
Private Key Security
- Use Hardware Wallets: Use hardware wallets for maximum security
- Secure Storage: Store private keys in secure locations
- Backup Keys: Create secure backups of private keys
- Never Share: Never share private keys with anyone
Transaction Security
- Verify Recipients: Always verify recipient addresses
- Check Gas Limits: Ensure adequate gas limits
- Review Transactions: Review transactions before signing
- Use Testnet: Test on testnet before mainnet
Security Monitoring
Event-Based Monitoring
// Monitor for security events
class SecurityEventMonitor {
constructor(contract) {
this.contract = contract;
this.suspiciousActivity = [];
this.setupMonitoring();
}
setupMonitoring() {
// Monitor for rapid minting
this.contract.on('IdentityMinted', (to, tokenId, name, event) => {
this.checkRapidMinting(to, event);
});
// Monitor for activation patterns
this.contract.on('IdentityActivated', (tokenId, owner, event) => {
this.checkActivationPatterns(tokenId, owner, event);
});
}
checkRapidMinting(to, event) {
const now = Date.now();
const recentMints = this.suspiciousActivity.filter(
activity => activity.type === 'mint' &&
activity.to === to &&
(now - activity.timestamp) < 60000 // 1 minute
);
if (recentMints.length > 5) {
console.warn(`Suspicious activity: Rapid minting detected for ${to}`);
this.suspiciousActivity.push({
type: 'rapid_minting',
to,
timestamp: now,
blockNumber: event.blockNumber
});
}
}
checkActivationPatterns(tokenId, owner, event) {
// Check for activation patterns
const recentActivations = this.suspiciousActivity.filter(
activity => activity.type === 'activation' &&
activity.owner === owner &&
(Date.now() - activity.timestamp) < 300000 // 5 minutes
);
if (recentActivations.length > 10) {
console.warn(`Suspicious activity: Rapid activation detected for ${owner}`);
}
}
}
Access Control Monitoring
// Monitor access control changes
class AccessControlMonitor {
constructor(contract) {
this.contract = contract;
this.setupRoleMonitoring();
}
setupRoleMonitoring() {
// Monitor for role changes
this.contract.on('RoleGranted', (role, account, sender, event) => {
this.logRoleGranted(role, account, sender, event);
});
this.contract.on('RoleRevoked', (role, account, sender, event) => {
this.logRoleRevoked(role, account, sender, event);
});
}
logRoleGranted(role, account, sender, event) {
console.log(`Security: Role granted`, {
role: role.toString(),
account,
sender,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash
});
}
logRoleRevoked(role, account, sender, event) {
console.log(`Security: Role revoked`, {
role: role.toString(),
account,
sender,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash
});
}
}
Incident Response
Security Incident Response Plan
- Detection: Monitor for suspicious activity
- Assessment: Assess the severity of the incident
- Containment: Take immediate action to contain the incident
- Investigation: Investigate the root cause
- Recovery: Implement recovery measures
- Prevention: Implement preventive measures
Emergency Procedures
// Emergency response procedures
class EmergencyResponse {
constructor(contract) {
this.contract = contract;
this.emergencyMode = false;
}
// Activate emergency mode
activateEmergencyMode() {
this.emergencyMode = true;
console.log('Emergency mode activated');
// Stop all non-essential operations
this.stopNonEssentialOperations();
// Notify security team
this.notifySecurityTeam();
}
// Deactivate emergency mode
deactivateEmergencyMode() {
this.emergencyMode = false;
console.log('Emergency mode deactivated');
}
stopNonEssentialOperations() {
// Stop non-essential operations
console.log('Non-essential operations stopped');
}
notifySecurityTeam() {
// Notify security team
console.log('Security team notified');
}
}
Security Updates
Regular Security Updates
- Monitor Security Advisories: Stay updated with security advisories
- Update Dependencies: Keep dependencies updated
- Security Patches: Apply security patches promptly
- Security Reviews: Conduct regular security reviews
Security Communication
- Security Notices: Publish security notices when needed
- Vulnerability Disclosure: Follow responsible disclosure practices
- Security Updates: Communicate security updates to users
- Incident Reports: Publish incident reports when appropriate