Skip to main content

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:

  1. Capability bitmask -- A 256-bit namespace defining all possible permissions
  2. Attestation provider abstraction -- Pluggable verification backends with the EAS reference implementation
  3. 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 records

Tier 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:

RoleCapabilitiesUse Case
ROLE_VIEWERCORE_VIEWRead-only observers, auditors
ROLE_PARTICIPANTCORE_VIEW | CORE_CLAIM | CORE_TRANSFER | FIN_REQUEST_PAYMENTStandard record participants (buyers, sellers)
ROLE_MANAGERROLE_PARTICIPANT | CORE_UPDATE | FIN_APPROVE_PAYMENT | DOC_SIGN | DOC_WITNESSOperational managers, authorized agents
ROLE_ADMINAll bits 0-127Full 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 requiresCapability modifier 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 executes

Provider 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:

  1. Fetch attestation from EAS contract
  2. Verify attestation exists (UID is not zero)
  3. Verify not revoked
  4. Verify not expired
  5. Verify schema matches expected schema UID
  6. Verify recipient matches caller (front-running protection)
  7. Verify attester is an authorized issuer
  8. Verify source chain ID matches current chain (cross-chain replay prevention)
  9. Verify source EAS contract matches (EAS spoofing prevention)
  10. Verify record contract matches (contract spoofing prevention)
  11. Verify schema version
  12. Verify content hash matches expected record
  13. Verify attestation age (optional staleness check)

Attack Vectors Prevented

AttackPrevention
Front-running (proof theft)Step 6: recipient must match caller
Cross-chain replayStep 8: chain ID validation
Fake EAS contractStep 9: EAS address validation
Wrong recordStep 12: content hash validation
Stale attestationStep 13: optional age limit
Revoked permissionStep 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 exists

The 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)
StageDurationGovernorKey Properties
BOOTSTRAPMonths 0-6Team addressRapid iteration, bug fixes
MULTISIGMonths 6-12Guardian multisigDeliberate, multi-party approval
DAOMonths 12-24Community DAOOn-chain voting, full decentralization
OSSIFIEDMonth 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.