Access Control
How Integra's capability bitmask model, attestation providers, and progressive ossification governance control permissions across the protocol.
Overview
Integra implements a provider-agnostic, capability-based access control system that sits between record/token contracts and the attestation systems that prove a user's permissions. Instead of hard-coding a single attestation mechanism, the protocol defines a provider interface (IAttestationProvider) so that any compliant system -- EAS, Verifiable Credentials, ZK proofs, DIDs -- can plug in without changing downstream contracts.
At transaction time, the system answers one question: does this caller hold a valid attestation granting the capability required for this operation on this record?
The architecture has three layers:
- Capability bitmask -- A 256-bit namespace defining all possible permissions
- Attestation provider abstraction -- Pluggable verification backends with the EAS reference implementation
- Progressive ossification -- Governance that evolves from team control to permanent immutability
The Capability Bitmask Model
Permissions are encoded as a 256-bit integer where each bit position represents a distinct capability. The CapabilityNamespaceV1 contract defines the canonical bit positions, organized into four tiers:
Tier 0: Core Capabilities (Bits 0-7)
uint256 public constant CORE_VIEW = 1 << 0; // Read-only access
uint256 public constant CORE_CLAIM = 1 << 1; // Claim reserved tokens
uint256 public constant CORE_TRANSFER = 1 << 2; // Transfer token ownership
uint256 public constant CORE_UPDATE = 1 << 3; // Update metadata
uint256 public constant CORE_DELEGATE = 1 << 4; // Delegate capabilities
uint256 public constant CORE_REVOKE = 1 << 5; // Revoke access
uint256 public constant CORE_ADMIN = 1 << 7; // Superuser (implies all)Tier 1: Record Operations (Bits 8-15)
uint256 public constant DOC_SIGN = 1 << 8; // Sign records
uint256 public constant DOC_WITNESS = 1 << 9; // Witness signatures
uint256 public constant DOC_NOTARIZE = 1 << 10; // Notarize records
uint256 public constant DOC_VERIFY = 1 << 11; // Verify authenticity
uint256 public constant DOC_AMEND = 1 << 12; // Amend content
uint256 public constant DOC_ARCHIVE = 1 << 13; // Archive recordsTier 2: Financial Operations (Bits 16-23)
uint256 public constant FIN_REQUEST_PAYMENT = 1 << 16;
uint256 public constant FIN_APPROVE_PAYMENT = 1 << 17;
uint256 public constant FIN_EXECUTE_PAYMENT = 1 << 18;
uint256 public constant FIN_CANCEL_PAYMENT = 1 << 19;
uint256 public constant FIN_WITHDRAW = 1 << 20;
uint256 public constant FIN_DEPOSIT = 1 << 21;Tier 3: Governance Operations (Bits 24-31)
uint256 public constant GOV_PROPOSE = 1 << 24;
uint256 public constant GOV_VOTE = 1 << 25;
uint256 public constant GOV_EXECUTE = 1 << 26;
uint256 public constant GOV_VETO = 1 << 27;
uint256 public constant GOV_DELEGATE_VOTE = 1 << 28;Bits 32-255 are reserved for future protocol extensions.
Capability Checking
The CORE_ADMIN bit (bit 7) acts as a superuser flag. When set, hasCapability returns true for any requested capability:
function hasCapability(uint256 granted, uint256 required) external pure returns (bool) {
return ((granted & CORE_ADMIN) != 0) || ((granted & required) == required);
}Capabilities compose with bitwise OR:
// Grant both claim and transfer
uint256 capabilities = CORE_CLAIM | CORE_TRANSFER;
// Add a capability to an existing set
uint256 updated = capabilities | FIN_REQUEST_PAYMENT;
// Remove a capability
uint256 reduced = capabilities & ~CORE_TRANSFER;Pre-Composed Role Templates
CapabilityNamespaceV1 provides four role templates for common permission sets:
| Role | Capabilities | Use Case |
|---|---|---|
ROLE_VIEWER | CORE_VIEW | Read-only observers, auditors |
ROLE_PARTICIPANT | CORE_VIEW | CORE_CLAIM | CORE_TRANSFER | FIN_REQUEST_PAYMENT | Standard record participants (buyers, sellers) |
ROLE_MANAGER | ROLE_PARTICIPANT | CORE_UPDATE | FIN_APPROVE_PAYMENT | DOC_SIGN | DOC_WITNESS | Operational managers, authorized agents |
ROLE_ADMIN | All bits 0-127 | Full administrative access |
These templates are convenience constants. You can also compose custom capability sets by combining individual bits.
AttestationAccessControlV1
AttestationAccessControlV1 is the abstract base contract that all Integra contracts needing attestation-gated access inherit from. It provides:
- The
requiresCapabilitymodifier for protecting functions - Provider resolution logic (per-record override or global default)
- Progressive ossification governance
- Emergency pause/unpause
How Verification Works
1. Protected function is called with an attestation proof
2. requiresCapability modifier triggers _verifyCapability()
3. Provider is resolved: per-record override or default
4. Provider address is looked up from IntegraRegistryV1 (with code-hash check)
5. Provider's verifyCapabilities() is called with the proof
6. Returned capability bitmask is checked against the required capability
7. If all checks pass, function body executesProvider Selection
Each record can use a different attestation provider:
// Global default (set by GOVERNOR_ROLE)
bytes32 public defaultProviderId;
// Per-record override (set by EXECUTOR_ROLE)
mapping(bytes32 => bytes32) public recordProvider;When recordProvider[contentHash] is bytes32(0), the default provider is used. This allows different records to use different attestation systems while maintaining a sensible default.
Integration Pattern
Contracts that need attestation-gated access annotate protected functions:
contract MyTokenizer is AttestationAccessControlV1 {
function updateMetadata(
bytes32 contentHash,
string calldata newUri,
bytes32 attestationUID
)
external
requiresCapabilityWithUID(contentHash, NAMESPACE.CORE_UPDATE(), attestationUID)
{
// Executes only if attestation grants CORE_UPDATE
}
}EAS Integration (EASAttestationProviderV1)
The Ethereum Attestation Service (EAS) is the reference attestation provider. EAS is an open-source, permissionless attestation infrastructure deployed across 16+ chains. EASAttestationProviderV1 implements the IAttestationProvider interface and performs a 13-step verification process:
- Fetch attestation from EAS contract
- Verify attestation exists (UID is not zero)
- Verify not revoked
- Verify not expired
- Verify schema matches expected schema UID
- Verify recipient matches caller (front-running protection)
- Verify attester is an authorized issuer
- Verify source chain ID matches current chain (cross-chain replay prevention)
- Verify source EAS contract matches (EAS spoofing prevention)
- Verify record contract matches (contract spoofing prevention)
- Verify schema version
- Verify content hash matches expected record
- Verify attestation age (optional staleness check)
Attack Vectors Prevented
| Attack | Prevention |
|---|---|
| Front-running (proof theft) | Step 6: recipient must match caller |
| Cross-chain replay | Step 8: chain ID validation |
| Fake EAS contract | Step 9: EAS address validation |
| Wrong record | Step 12: content hash validation |
| Stale attestation | Step 13: optional age limit |
| Revoked permission | Step 3: revocation check |
Issuing an Access Attestation
Using the EAS SDK (JavaScript):
import { EAS, SchemaEncoder } from "@ethereum-attestation-service/eas-sdk";
const eas = new EAS(EAS_CONTRACT_ADDRESS);
eas.connect(signer);
const schemaEncoder = new SchemaEncoder(
"bytes32 contentHash,uint256 tokenId,uint256 capabilities," +
"string verifiedIdentity,string verificationMethod," +
"uint256 verificationDate,string contractRole," +
"string legalEntityType,string notes,uint256 sourceChainId," +
"address sourceEASContract,address recordContract," +
"uint64 issuedAt,bytes32 attestationVersion"
);
const encodedData = schemaEncoder.encodeData([
{ name: "contentHash", value: integraHash, type: "bytes32" },
{ name: "tokenId", value: 1, type: "uint256" },
{ name: "capabilities", value: 0x03, type: "uint256" }, // CLAIM + TRANSFER
{ name: "verifiedIdentity", value: "John Doe", type: "string" },
{ name: "verificationMethod", value: "KYC:Provider", type: "string" },
{ name: "verificationDate", value: Math.floor(Date.now() / 1000), type: "uint256" },
{ name: "contractRole", value: "Buyer", type: "string" },
{ name: "legalEntityType", value: "Individual", type: "string" },
{ name: "notes", value: "", type: "string" },
{ name: "sourceChainId", value: 8453, type: "uint256" },
{ name: "sourceEASContract", value: EAS_CONTRACT_ADDRESS, type: "address" },
{ name: "recordContract", value: RECORD_CONTRACT_ADDRESS, type: "address" },
{ name: "issuedAt", value: Math.floor(Date.now() / 1000), type: "uint64" },
{ name: "attestationVersion", value: ethers.id("INTEGRA_CAPABILITY_V1"), type: "bytes32" }
]);
const tx = await eas.attest({
schema: ACCESS_CAPABILITY_SCHEMA_UID,
data: {
recipient: userAddress,
expirationTime: 0, // No expiration
revocable: true,
data: encodedData,
},
});
const attestationUID = await tx.wait();Revoking Access
await eas.revoke({
schema: ACCESS_CAPABILITY_SCHEMA_UID,
data: { uid: attestationUID }
});Provider Abstraction
The IAttestationProvider interface allows any attestation system to plug into Integra:
interface IAttestationProvider {
function verifyCapabilities(
bytes calldata proof,
address recipient,
bytes32 contentHash,
uint256 requiredCapability
) external view returns (bool verified, uint256 grantedCapabilities);
function getProviderInfo()
external view returns (string memory name, string memory version, bytes32 providerType);
function supportsMethod(bytes32 methodId) external view returns (bool);
}The proof parameter is opaque bytes whose format is defined by each provider. For EAS, it is abi.encode(attestationUID). For a ZK provider, it might be a Groth16 proof. For a Verifiable Credential provider, it might be a JWT.
Providers are registered in IntegraRegistryV1 with code-hash integrity verification. If a provider contract is upgraded or redeployed, the registry returns address(0) until the new version is re-registered. This prevents malicious provider substitution.
Per-Record Executor Authorization
Beyond attestation-based capabilities, each record supports a zero-trust executor model:
ACCESS PATHS (priority order):
1. RECORD OWNER -- Always has full access, cannot be revoked
2. PER-RECORD EXECUTOR -- Explicitly authorized by owner, revocable
3. NO FALLBACK -- No global executor role existsThe owner can authorize a backend server, DAO, multisig, or custom contract as the executor for a specific record. This is an opt-in model with zero-trust defaults:
// Owner authorizes a backend executor
integraRecordV1.authorizeRecordExecutor(integraHash, backendAddress);
// Owner revokes authorization
integraRecordV1.revokeRecordExecutor(integraHash);If an executor is compromised, only the specific records it was authorized for are at risk -- not the entire protocol.
Progressive Ossification
Governance of AttestationAccessControlV1 evolves through four irreversible stages:
BOOTSTRAP --> MULTISIG --> DAO --> OSSIFIED
(team) (3-of-5) (community) (permanent)| Stage | Duration | Governor | Key Properties |
|---|---|---|---|
| BOOTSTRAP | Months 0-6 | Team address | Rapid iteration, bug fixes |
| MULTISIG | Months 6-12 | Guardian multisig | Deliberate, multi-party approval |
| DAO | Months 12-24 | Community DAO | On-chain voting, full decentralization |
| OSSIFIED | Month 24+ | None (frozen) | No more upgrades, permanent immutability |
Transitions are one-way. The current governor initiates the transition to the next stage, transferring GOVERNOR_ROLE to the new entity and revoking it from the old one. Once ossified, _authorizeUpgrade() reverts unconditionally -- the contract can never be upgraded again.
After ossification, the DAO retains operational capabilities (granting roles, updating providers, pausing in emergencies) but cannot change the governance model itself.
Multi-Layer Security in Practice
A typical token claim operation requires all three access control layers to pass:
function claimToken(
bytes32 integraHash,
uint256 tokenId,
bytes calldata attestationProof
) external
requiresCapability(integraHash, CORE_CLAIM, attestationProof) // Layer 1: Attestation
requireOwnerOrExecutor(integraHash) // Layer 2: Authorization
nonReentrant
whenNotPaused
{
// All layers passed -- execute claim
}Even if the attestation system is compromised, the attacker still needs to be the record owner or an authorized executor. Even if an executor is compromised, the attestation limits which capabilities are available. This defense-in-depth approach ensures no single point of failure can grant unauthorized access.