Revocation Registry Smart Contract

View as Markdown

The Revocation Registry manages credential revocation status on the ADI blockchain. It supports single and batch revocation operations with reason codes, and provides on-chain verification of credential status.

Contract Address

Deployed to ADI devnet. See deployments/ for current addresses.

Interface

1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.24;
3
4interface IRevocationRegistry {
5 struct RevocationRecord {
6 bool revoked;
7 string reason;
8 address issuer;
9 uint256 revokedAt;
10 }
11
12 event CredentialRevoked(
13 string indexed credentialIdHash,
14 string credentialId,
15 address indexed issuer,
16 string reason,
17 uint256 timestamp
18 );
19
20 function revokeCredential(string calldata credentialId, string calldata reason) external;
21
22 function isRevoked(string calldata credentialId)
23 external view returns (bool revoked, string memory reason, uint256 revokedAt);
24
25 function batchRevoke(string[] calldata credentialIds, string calldata reason) external;
26}

Key Functions

revokeCredential

Revokes a single credential. The caller (msg.sender) is recorded as the issuer. If already revoked by the same issuer, the reason and timestamp are updated.

1function revokeCredential(string calldata credentialId, string calldata reason) external;
ParameterTypeDescription
credentialIdstringThe credential identifier (e.g., urn:uuid:...)
reasonstringHuman-readable revocation reason

Reverts if:

  • credentialId is empty
  • Credential was already revoked by a different address

isRevoked

Check whether a credential has been revoked. This is a view function (no gas cost for external calls).

1function isRevoked(string calldata credentialId)
2 external view returns (bool revoked, string memory reason, uint256 revokedAt);
ReturnTypeDescription
revokedboolWhether the credential is revoked
reasonstringThe revocation reason (empty if not revoked)
revokedAtuint256Unix timestamp of revocation (0 if not revoked)

batchRevoke

Revoke multiple credentials in a single transaction. All credentials receive the same reason string.

1function batchRevoke(string[] calldata credentialIds, string calldata reason) external;
ParameterTypeDescription
credentialIdsstring[]Array of credential identifiers (max 100)
reasonstringRevocation reason applied to all

Reverts if:

  • Array is empty
  • Array exceeds 100 entries
  • Any credential was already revoked by a different address

getIssuer

Returns the address that revoked a credential.

1function getIssuer(string calldata credentialId) external view returns (address);

Returns the zero address if the credential has not been revoked.

Revocation Reason Codes

The API layer maps numeric reason codes to human-readable strings before calling the contract:

CodeLabelDescription
0unspecifiedNo specific reason given
1key_compromiseIssuer’s signing key was compromised
2issuer_compromiseIssuer entity was compromised
3affiliation_changedSubject’s affiliation has changed
4supersededCredential replaced by a newer version
5cessation_of_operationIssuer has ceased operations
6agent_decommissionedAI agent has been decommissioned
7privilege_withdrawnPrivileges granted by credential withdrawn
8fraud_detectedFraudulent credential detected

Storage

Credential IDs are hashed with keccak256 before use as mapping keys:

1mapping(bytes32 => RevocationRecord) private _revocations;
2
3function _hash(string calldata credentialId) internal pure returns (bytes32) {
4 return keccak256(abi.encodePacked(credentialId));
5}

Security

  • ReentrancyGuard: All state-mutating functions use OpenZeppelin’s nonReentrant modifier.
  • Issuer enforcement: Only the original revoker can update the reason on an already-revoked credential.
  • Batch limit: Maximum 100 credentials per batch to prevent gas limit issues.

Interaction Examples

JavaScript (ethers.js)

1const { ethers } = require('ethers');
2
3const revocationRegistry = new ethers.Contract(
4 REVOCATION_REGISTRY_ADDRESS,
5 IRevocationRegistryABI,
6 signer
7);
8
9// Check if a credential is revoked
10const [revoked, reason, revokedAt] = await revocationRegistry.isRevoked(
11 'urn:uuid:12345678-1234-1234-1234-123456789abc'
12);
13console.log('Revoked:', revoked, 'Reason:', reason);
14
15// Revoke a single credential
16const tx = await revocationRegistry.revokeCredential(
17 'urn:uuid:12345678-1234-1234-1234-123456789abc',
18 'key_compromise'
19);
20await tx.wait();
21
22// Batch revoke (e.g., agent decommissioning)
23const credentialIds = [
24 'urn:uuid:agent-cred-001',
25 'urn:uuid:agent-cred-002',
26 'urn:uuid:agent-cred-003',
27];
28const batchTx = await revocationRegistry.batchRevoke(
29 credentialIds,
30 'agent_decommissioned'
31);
32await batchTx.wait();
33
34// Get the issuer who revoked
35const issuerAddress = await revocationRegistry.getIssuer(
36 'urn:uuid:12345678-1234-1234-1234-123456789abc'
37);

Go (API integration)

The RevocationService in packages/api/pkg/credential/revocation.go provides a Go interface that coordinates between the local PostgreSQL database and the on-chain contract:

1svc := credential.NewRevocationService(store, chainClient, statusMgr, logger)
2
3// Revoke with reason code
4entry, err := svc.RevokeCredential(ctx, credentialID, issuerDID, credential.ReasonKeyCompromise)
5
6// Check combined status (DB + on-chain)
7result, err := svc.CheckRevocationStatus(ctx, credentialID)
8fmt.Println("Revoked:", result.Revoked, "In DB:", result.RevokedInDB, "On-chain:", result.RevokedOnChain)
9
10// Batch revoke
11entries, err := svc.BatchRevoke(ctx, credentialIDs, issuerDID, credential.ReasonAgentDecommissioned)

StatusList2021 Integration

The platform also supports the W3C StatusList2021 bitstring-based status tracking via packages/api/pkg/credential/status.go. Each credential is assigned an index in a bitstring, and the status list is published as a Verifiable Credential.

1sl := credential.NewStatusList2021(issuerDID, credential.StatusPurposeRevocation, 16384)
2
3// Allocate an index for a new credential
4index, _ := sl.AllocateIndex()
5
6// Revoke by setting the bit
7sl.SetStatus(index, true)
8
9// Generate the status list credential for publication
10statusListVC := sl.GenerateStatusListCredential()

Gas Costs

OperationEstimated Gas
revokeCredential~50,000
batchRevoke (10 items)~120,000
batchRevoke (100 items)~800,000
isRevoked0 (view function)
getIssuer0 (view function)
FileDescription
packages/blockchain/contracts/RevocationRegistry.solSolidity contract implementation
packages/blockchain/contracts/interfaces/IRevocationRegistry.solContract interface
packages/api/pkg/credential/revocation.goGo revocation service
packages/api/pkg/credential/status.goStatusList2021 implementation
packages/api/internal/service/vc_service.goVC service with revocation methods
packages/api/internal/handler/vc.goHTTP handlers for revocation endpoints