Files
self/contracts/contracts/IdentityVerificationHubImplV2.sol
2026-01-28 15:46:49 +05:30

1082 lines
48 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import {ImplRoot} from "./upgradeable/ImplRoot.sol";
import {SelfStructs} from "./libraries/SelfStructs.sol";
import {GenericProofStruct} from "./interfaces/IRegisterCircuitVerifier.sol";
import {CustomVerifier} from "./libraries/CustomVerifier.sol";
import {GenericFormatter} from "./libraries/GenericFormatter.sol";
import {AttestationId} from "./constants/AttestationId.sol";
import {ISelfVerificationRoot} from "./interfaces/ISelfVerificationRoot.sol";
import {IIdentityRegistryV1} from "./interfaces/IIdentityRegistryV1.sol";
import {IIdentityRegistryIdCardV1} from "./interfaces/IIdentityRegistryIdCardV1.sol";
import {IIdentityRegistryAadhaarV1} from "./interfaces/IIdentityRegistryAadhaarV1.sol";
import {IIdentityRegistryKycV1} from "./interfaces/IIdentityRegistryKycV1.sol";
import {IDscCircuitVerifier} from "./interfaces/IDscCircuitVerifier.sol";
import {CircuitConstantsV2} from "./constants/CircuitConstantsV2.sol";
import {Formatter} from "./libraries/Formatter.sol";
import {OutputFormatterLib} from "./libraries/OutputFormatterLib.sol";
import {ProofVerifierLib} from "./libraries/ProofVerifierLib.sol";
import {RegisterProofVerifierLib} from "./libraries/RegisterProofVerifierLib.sol";
import {DscProofVerifierLib} from "./libraries/DscProofVerifierLib.sol";
import {RootCheckLib} from "./libraries/RootCheckLib.sol";
import {OfacCheckLib} from "./libraries/OfacCheckLib.sol";
import {console} from "hardhat/console.sol";
/**
* @title IdentityVerificationHubImplV2
* @notice Main hub for identity verification in the Self Protocol
* @dev This contract orchestrates multi-step verification processes including document attestation,
* zero-knowledge proofs, OFAC compliance, and attribute disclosure control.
*
* @custom:version 2.12.0
*/
contract IdentityVerificationHubImplV2 is ImplRoot {
/// @custom:storage-location erc7201:self.storage.IdentityVerificationHub
struct IdentityVerificationHubStorage {
uint256 _circuitVersion;
mapping(bytes32 attestationId => address registry) _registries;
mapping(bytes32 attestationId => mapping(uint256 sigTypeId => address registerCircuitVerifier)) _registerCircuitVerifiers;
mapping(bytes32 attestationId => mapping(uint256 sigTypeId => address dscCircuitVerifier)) _dscCircuitVerifiers;
mapping(bytes32 attestationId => address discloseVerifiers) _discloseVerifiers;
}
/// @custom:storage-location erc7201:self.storage.IdentityVerificationHubV2
struct IdentityVerificationHubV2Storage {
mapping(bytes32 configId => SelfStructs.VerificationConfigV2) _v2VerificationConfigs;
}
// We should consider to add bridge address
// address bridgeAddress;
/// @dev keccak256(abi.encode(uint256(keccak256("self.storage.IdentityVerificationHub")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant IDENTITYVERIFICATIONHUB_STORAGE_LOCATION =
0x2ade7eace21710c689ddef374add52ace9783e33bac626e58e73a9d190173d00;
/// @dev keccak256(abi.encode(uint256(keccak256("self.storage.IdentityVerificationHubV2")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant IDENTITYVERIFICATIONHUBV2_STORAGE_LOCATION =
0xf9b5980dcec1a8b0609576a1f453bb2cad4732a0ea02bb89154d44b14a306c00;
/// @notice The AADHAAR registration window around the current block timestamp.
uint256 public AADHAAR_REGISTRATION_WINDOW;
/**
* @notice Returns the storage struct for the main IdentityVerificationHub.
* @dev Uses ERC-7201 storage pattern for upgradeable contracts.
* @return $ The storage struct reference.
*/
function _getIdentityVerificationHubStorage() private pure returns (IdentityVerificationHubStorage storage $) {
assembly {
$.slot := IDENTITYVERIFICATIONHUB_STORAGE_LOCATION
}
}
/**
* @notice Returns the storage struct for IdentityVerificationHub V2 features.
* @dev Uses ERC-7201 storage pattern for upgradeable contracts.
* @return $ The V2 storage struct reference.
*/
function _getIdentityVerificationHubV2Storage() private pure returns (IdentityVerificationHubV2Storage storage $) {
assembly {
$.slot := IDENTITYVERIFICATIONHUBV2_STORAGE_LOCATION
}
}
/**
* @notice Emitted when the Hub V2 is successfully initialized.
*/
event HubInitializedV2();
/**
* @notice Emitted when a verification config V2 is set.
* @param configId The configuration identifier (generated from config hash).
* @param config The verification configuration that was set.
*/
event VerificationConfigV2Set(bytes32 indexed configId, SelfStructs.VerificationConfigV2 config);
/**
* @notice Emitted when the registry address is updated.
* @param attestationId The attestation identifier.
* @param registry The new registry address.
*/
event RegistryUpdated(bytes32 attestationId, address registry);
/**
* @notice Emitted when the VC and Disclose circuit verifier is updated.
* @param attestationId The attestation identifier.
* @param vcAndDiscloseCircuitVerifier The new VC and Disclose circuit verifier address.
*/
event VcAndDiscloseCircuitUpdated(bytes32 attestationId, address vcAndDiscloseCircuitVerifier);
/**
* @notice Emitted when a register circuit verifier is updated.
* @param typeId The signature type id.
* @param verifier The new verifier address for the register circuit.
*/
event RegisterCircuitVerifierUpdated(uint256 typeId, address verifier);
/**
* @notice Emitted when a DSC circuit verifier is updated.
* @param typeId The signature type id.
* @param verifier The new verifier address for the DSC circuit.
*/
event DscCircuitVerifierUpdated(uint256 typeId, address verifier);
/**
* @notice Emitted when a verification is performed.
* @param requestor The contract that initiated the verification request.
* @param contractVersion The contract version used for verification output formatting.
* @param attestationId The attestation identifier (E_PASSPORT or EU_ID_CARD).
* @param destChainId The destination chain ID.
* @param configId The configuration ID.
* @param userIdentifier The user identifier.
* @param output The formatted verification output containing proof results.
* @param userDataToPass The user data passed through to the verification result handler.
*/
event DisclosureVerified(
address indexed requestor,
uint8 indexed contractVersion,
bytes32 indexed attestationId,
uint256 destChainId,
bytes32 configId,
uint256 userIdentifier,
bytes output,
bytes userDataToPass
);
// ====================================================
// Errors
// ====================================================
/// @notice Thrown when arrays have mismatched lengths in batch operations.
/// @dev Ensures that all input arrays have the same length for batch updates.
error LengthMismatch();
/// @notice Thrown when no verifier is set for a given signature type.
/// @dev Indicates that the mapping lookup for the verifier returned the zero address.
error NoVerifierSet();
/// @notice Thrown when the current date in the proof is not within the valid range.
/// @dev Ensures that the provided proof's date is within one day of the expected start time.
error CurrentDateNotInValidRange();
/// @notice Thrown when the register circuit proof is invalid.
/// @dev The register circuit verifier did not validate the provided proof.
error InvalidRegisterProof();
/// @notice Thrown when the DSC circuit proof is invalid.
/// @dev The DSC circuit verifier did not validate the provided proof.
error InvalidDscProof();
/// @notice Thrown when the VC and Disclose proof is invalid.
/// @dev The VC and Disclose circuit verifier did not validate the provided proof.
error InvalidVcAndDiscloseProof();
/// @notice Thrown when the provided identity commitment root is invalid.
/// @dev Used in proofs to ensure that the identity commitment root matches the expected value in the registry.
error InvalidIdentityCommitmentRoot();
/// @notice Thrown when the provided DSC commitment root is invalid.
/// @dev Used in proofs to ensure that the DSC commitment root matches the expected value in the registry.
error InvalidDscCommitmentRoot();
/// @notice Thrown when the provided CSCA root is invalid.
/// @dev Indicates that the CSCA root from the DSC proof does not match the expected CSCA root.
error InvalidCscaRoot();
/// @notice Thrown when an invalid attestation ID is provided.
/// @dev The attestation ID must be a supported type (e.g., E_PASSPORT or EU_ID_CARD).
error InvalidAttestationId();
/// @notice Thrown when the scope in the header doesn't match the scope in the proof.
/// @dev Ensures that the scope value in the header matches the scope value in the proof.
error ScopeMismatch();
/// @notice Thrown when cross-chain verification is attempted but not yet supported.
/// @dev Cross-chain bridging functionality is not implemented yet.
error CrossChainIsNotSupportedYet();
/// @notice Thrown when the input data is too short for decoding.
/// @dev The input data must be at least 97 bytes (1 + 31 + 32 + 32 + 1 minimum).
error InputTooShort();
/// @notice Thrown when the user context data is too short for decoding.
/// @dev The user context data must be at least 96 bytes (32 + 32 + 32 minimum).
error UserContextDataTooShort();
/// @notice Thrown when the user identifier hash does not match the proof user identifier.
/// @dev Ensures that the user context data hash matches the user identifier in the proof.
error InvalidUserIdentifierInProof();
/// @notice Thrown when the verification config is not set.
/// @dev Ensures that the verification config is set before performing verification.
error ConfigNotSet();
/// @notice Thrown when the pubkey is not valid.
/// @dev Ensures that the pubkey is valid.
error InvalidPubkey();
/// @notice Thrown when the timestamp is invalid.
/// @dev Ensures that the timestamp is within 20 minutes of the current block timestamp.
error InvalidUidaiTimestamp(uint256 blockTimestamp, uint256 timestamp);
/// @notice Thrown when the attestationId in the proof doesn't match the header.
/// @dev Ensures that the attestationId in the proof matches the header.
error AttestationIdMismatch();
/// @notice Thrown when the ofac roots don't match.
/// @dev Ensures that the ofac roots match.
error InvalidOfacRoots();
/// @notice Thrown when the pubkey commitment is invalid.
/// @dev Ensures that the pubkey commitment is valid.
error InvalidPubkeyCommitment();
// ====================================================
// Constructor
// ====================================================
/**
* @notice Constructor that disables initializers for the implementation contract.
* @dev This prevents the implementation contract from being initialized directly.
* The actual initialization should only happen through the proxy.
* @custom:oz-upgrades-unsafe-allow constructor
*/
constructor() {
_disableInitializers();
}
// ====================================================
// Initializer
// ====================================================
/**
* @notice Initializes the Identity Verification Hub V2 contract for upgrade.
* @dev Sets up the contract state including circuit version and emits initialization event.
* This function is used when upgrading from V1 to V2, hence uses reinitializer(2).
* The circuit version is set to 2 for V2 hub compatibility.
*/
function initialize() external reinitializer(11) {
__ImplRoot_init();
// Initialize circuit version to 2 for V2 hub
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
$._circuitVersion = 2;
// Initialize Aadhaar registration window
AADHAAR_REGISTRATION_WINDOW = 20;
emit HubInitializedV2();
}
/**
* @notice Initializes governance for upgraded contracts.
* @dev Used when upgrading from Ownable to AccessControl governance.
* This function sets up AccessControl roles on an already-initialized contract.
* It does NOT modify existing state (hub, roots, etc.).
*
* SECURITY: This function can only be called once - enforced by reinitializer(12).
* The previous version used reinitializer(11), so this upgrade uses version 12.
*/
function initializeGovernance() external reinitializer(12) {
__ImplRoot_init();
}
// ====================================================
// External Functions
// ====================================================
/**
* @notice Registers a commitment using a register circuit proof.
* @dev Verifies the register circuit proof and then calls the Identity Registry to register the commitment.
* @param attestationId The attestation ID.
* @param registerCircuitVerifierId The identifier for the register circuit verifier to use.
* @param registerCircuitProof The register circuit proof data.
*/
function registerCommitment(
bytes32 attestationId,
uint256 registerCircuitVerifierId,
GenericProofStruct memory registerCircuitProof
) external virtual onlyProxy {
_verifyRegisterProof(attestationId, registerCircuitVerifierId, registerCircuitProof);
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
if (attestationId == AttestationId.E_PASSPORT) {
IIdentityRegistryV1($._registries[attestationId]).registerCommitment(
attestationId,
registerCircuitProof.pubSignals[CircuitConstantsV2.REGISTER_NULLIFIER_INDEX],
registerCircuitProof.pubSignals[CircuitConstantsV2.REGISTER_COMMITMENT_INDEX]
);
} else if (attestationId == AttestationId.EU_ID_CARD) {
IIdentityRegistryIdCardV1($._registries[attestationId]).registerCommitment(
attestationId,
registerCircuitProof.pubSignals[CircuitConstantsV2.REGISTER_NULLIFIER_INDEX],
registerCircuitProof.pubSignals[CircuitConstantsV2.REGISTER_COMMITMENT_INDEX]
);
} else if (attestationId == AttestationId.AADHAAR) {
IIdentityRegistryAadhaarV1($._registries[attestationId]).registerCommitment(
registerCircuitProof.pubSignals[CircuitConstantsV2.AADHAAR_NULLIFIER_INDEX],
registerCircuitProof.pubSignals[CircuitConstantsV2.AADHAAR_COMMITMENT_INDEX]
);
} else if (attestationId == AttestationId.KYC) {
IIdentityRegistryKycV1($._registries[attestationId]).registerCommitment(
registerCircuitProof.pubSignals[CircuitConstantsV2.KYC_NULLIFIER_INDEX],
registerCircuitProof.pubSignals[CircuitConstantsV2.KYC_COMMITMENT_INDEX]
);
} else {
revert InvalidAttestationId();
}
}
/**
* @notice Registers a DSC key commitment using a DSC circuit proof.
* @dev Verifies the DSC proof and then calls the Identity Registry to register the dsc key commitment.
* @param dscCircuitVerifierId The identifier for the DSC circuit verifier to use.
* @param dscCircuitProof The DSC circuit proof data.
*/
function registerDscKeyCommitment(
bytes32 attestationId,
uint256 dscCircuitVerifierId,
IDscCircuitVerifier.DscCircuitProof memory dscCircuitProof
) external virtual onlyProxy {
_verifyDscProof(attestationId, dscCircuitVerifierId, dscCircuitProof);
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
if (attestationId == AttestationId.E_PASSPORT) {
IIdentityRegistryV1($._registries[attestationId]).registerDscKeyCommitment(
dscCircuitProof.pubSignals[CircuitConstantsV2.DSC_TREE_LEAF_INDEX]
);
} else if (attestationId == AttestationId.EU_ID_CARD) {
IIdentityRegistryIdCardV1($._registries[attestationId]).registerDscKeyCommitment(
dscCircuitProof.pubSignals[CircuitConstantsV2.DSC_TREE_LEAF_INDEX]
);
} else {
revert InvalidAttestationId();
}
}
/**
* @notice Sets verification config in V2 storage (owner only)
* @dev The configId is automatically generated from the config content using sha256(abi.encode(config))
* @param config The verification configuration
* @return configId The generated config ID
*/
function setVerificationConfigV2(
SelfStructs.VerificationConfigV2 memory config
) external virtual onlyProxy returns (bytes32 configId) {
configId = generateConfigId(config);
IdentityVerificationHubV2Storage storage $v2 = _getIdentityVerificationHubV2Storage();
$v2._v2VerificationConfigs[configId] = config;
emit VerificationConfigV2Set(configId, config);
}
/**
* @notice Updates the AADHAAR registration window.
* @param window The new AADHAAR registration window.
*/
function setAadhaarRegistrationWindow(uint256 window) external virtual onlyProxy onlyRole(SECURITY_ROLE) {
AADHAAR_REGISTRATION_WINDOW = window;
}
/**
* @notice Main verification function with new structured input format.
* @dev Orchestrates the complete verification process including proof validation and result handling.
* This function decodes the input, executes the verification flow, and handles the result based on destination chain.
* @param baseVerificationInput The base verification input containing header and proof data.
* @param userContextData The user context data containing config ID, destination chain ID, user identifier, and additional data.
*/
function verify(bytes calldata baseVerificationInput, bytes calldata userContextData) external virtual onlyProxy {
(SelfStructs.HubInputHeader memory header, bytes calldata proofData) = _decodeInput(baseVerificationInput);
// Perform verification and get output along with user data
(
bytes memory output,
uint256 destChainId,
bytes memory userDataToPass,
bytes32 configId,
uint256 userIdentifier
) = _executeVerificationFlow(header, proofData, userContextData);
// Use destChainId and userDataToPass returned from _executeVerificationFlow
_handleVerificationResult(destChainId, output, userDataToPass);
// Emit verification event for tracking
emit DisclosureVerified(
msg.sender,
header.contractVersion,
header.attestationId,
destChainId,
configId,
userIdentifier,
output,
userDataToPass
);
}
/**
* @notice Updates the registry address.
* @param registryAddress The new registry address.
*/
function updateRegistry(
bytes32 attestationId,
address registryAddress
) external virtual onlyProxy onlyRole(SECURITY_ROLE) {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
$._registries[attestationId] = registryAddress;
emit RegistryUpdated(attestationId, registryAddress);
}
/**
* @notice Updates the VC and Disclose circuit verifier address.
* @param vcAndDiscloseCircuitVerifierAddress The new VC and Disclose circuit verifier address.
*/
function updateVcAndDiscloseCircuit(
bytes32 attestationId,
address vcAndDiscloseCircuitVerifierAddress
) external virtual onlyProxy onlyRole(SECURITY_ROLE) {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
$._discloseVerifiers[attestationId] = vcAndDiscloseCircuitVerifierAddress;
emit VcAndDiscloseCircuitUpdated(attestationId, vcAndDiscloseCircuitVerifierAddress);
}
/**
* @notice Updates the register circuit verifier for a specific signature type.
* @param attestationId The attestation identifier.
* @param typeId The signature type identifier.
* @param verifierAddress The new register circuit verifier address.
*/
function updateRegisterCircuitVerifier(
bytes32 attestationId,
uint256 typeId,
address verifierAddress
) external virtual onlyProxy onlyRole(SECURITY_ROLE) {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
$._registerCircuitVerifiers[attestationId][typeId] = verifierAddress;
emit RegisterCircuitVerifierUpdated(typeId, verifierAddress);
}
/**
* @notice Updates the DSC circuit verifier for a specific signature type.
* @param attestationId The attestation identifier.
* @param typeId The signature type identifier.
* @param verifierAddress The new DSC circuit verifier address.
*/
function updateDscVerifier(
bytes32 attestationId,
uint256 typeId,
address verifierAddress
) external virtual onlyProxy onlyRole(SECURITY_ROLE) {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
$._dscCircuitVerifiers[attestationId][typeId] = verifierAddress;
emit DscCircuitVerifierUpdated(typeId, verifierAddress);
}
/**
* @notice Batch updates register circuit verifiers.
* @param attestationIds An array of attestation identifiers.
* @param typeIds An array of signature type identifiers.
* @param verifierAddresses An array of new register circuit verifier addresses.
*/
function batchUpdateRegisterCircuitVerifiers(
bytes32[] calldata attestationIds,
uint256[] calldata typeIds,
address[] calldata verifierAddresses
) external virtual onlyProxy onlyRole(SECURITY_ROLE) {
if (attestationIds.length != typeIds.length || attestationIds.length != verifierAddresses.length) {
revert LengthMismatch();
}
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
for (uint256 i = 0; i < attestationIds.length; i++) {
$._registerCircuitVerifiers[attestationIds[i]][typeIds[i]] = verifierAddresses[i];
emit RegisterCircuitVerifierUpdated(typeIds[i], verifierAddresses[i]);
}
}
/**
* @notice Batch updates DSC circuit verifiers.
* @param attestationIds An array of attestation identifiers.
* @param typeIds An array of signature type identifiers.
* @param verifierAddresses An array of new DSC circuit verifier addresses.
*/
function batchUpdateDscCircuitVerifiers(
bytes32[] calldata attestationIds,
uint256[] calldata typeIds,
address[] calldata verifierAddresses
) external virtual onlyProxy onlyRole(SECURITY_ROLE) {
if (attestationIds.length != typeIds.length || attestationIds.length != verifierAddresses.length) {
revert LengthMismatch();
}
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
for (uint256 i = 0; i < attestationIds.length; i++) {
$._dscCircuitVerifiers[attestationIds[i]][typeIds[i]] = verifierAddresses[i];
emit DscCircuitVerifierUpdated(typeIds[i], verifierAddresses[i]);
}
}
// ====================================================
// External View Functions
// ====================================================
/**
* @notice Returns the registry address for a given attestation ID.
* @param attestationId The attestation ID to query.
* @return The registry address associated with the attestation ID.
*/
function registry(bytes32 attestationId) external view virtual onlyProxy returns (address) {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
return $._registries[attestationId];
}
/**
* @notice Returns the disclose verifier address for a given attestation ID.
* @param attestationId The attestation ID to query.
* @return The disclose verifier address associated with the attestation ID.
*/
function discloseVerifier(bytes32 attestationId) external view virtual onlyProxy returns (address) {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
return $._discloseVerifiers[attestationId];
}
/**
* @notice Returns the register circuit verifier address for a given attestation ID and type ID.
* @param attestationId The attestation ID to query.
* @param typeId The type ID to query.
* @return The register circuit verifier address associated with the attestation ID and type ID.
*/
function registerCircuitVerifiers(
bytes32 attestationId,
uint256 typeId
) external view virtual onlyProxy returns (address) {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
return $._registerCircuitVerifiers[attestationId][typeId];
}
/**
* @notice Returns the DSC circuit verifier address for a given attestation ID and type ID.
* @param attestationId The attestation ID to query.
* @param typeId The type ID to query.
* @return The DSC circuit verifier address associated with the attestation ID and type ID.
*/
function dscCircuitVerifiers(
bytes32 attestationId,
uint256 typeId
) external view virtual onlyProxy returns (address) {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
return $._dscCircuitVerifiers[attestationId][typeId];
}
/**
* @notice Returns the merkle root timestamp for a given attestation ID and root.
* @param attestationId The attestation ID to query.
* @param root The merkle root to query.
* @return The merkle root timestamp associated with the attestation ID and root.
*/
function rootTimestamp(bytes32 attestationId, uint256 root) external view virtual onlyProxy returns (uint256) {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
address registryAddress = $._registries[attestationId];
if (attestationId == AttestationId.E_PASSPORT) {
return IIdentityRegistryV1(registryAddress).rootTimestamps(root);
} else if (attestationId == AttestationId.EU_ID_CARD) {
return IIdentityRegistryIdCardV1(registryAddress).rootTimestamps(root);
} else if (attestationId == AttestationId.AADHAAR) {
return IIdentityRegistryAadhaarV1(registryAddress).rootTimestamps(root);
} else {
revert InvalidAttestationId();
}
}
/**
* @notice Returns the identity commitment merkle root for a given attestation ID.
* @param attestationId The attestation ID to query.
* @return The identity commitment merkle root associated with the attestation ID.
*/
function getIdentityCommitmentMerkleRoot(bytes32 attestationId) external view virtual onlyProxy returns (uint256) {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
address registryAddress = $._registries[attestationId];
if (attestationId == AttestationId.E_PASSPORT) {
return IIdentityRegistryV1(registryAddress).getIdentityCommitmentMerkleRoot();
} else if (attestationId == AttestationId.EU_ID_CARD) {
return IIdentityRegistryIdCardV1(registryAddress).getIdentityCommitmentMerkleRoot();
} else if (attestationId == AttestationId.AADHAAR) {
return IIdentityRegistryAadhaarV1(registryAddress).getIdentityCommitmentMerkleRoot();
} else {
revert InvalidAttestationId();
}
}
/**
* @notice Checks if a verification config exists
* @param configId The configuration identifier
* @return exists Whether the config exists
*/
function verificationConfigV2Exists(bytes32 configId) external view virtual onlyProxy returns (bool exists) {
SelfStructs.VerificationConfigV2 memory config = getVerificationConfigV2(configId);
return generateConfigId(config) == configId;
}
// ====================================================
// Public Functions
// ====================================================
/**
* @notice Generates a config ID from a verification config
* @param config The verification configuration
* @return The generated config ID (sha256 hash of encoded config)
*/
function generateConfigId(SelfStructs.VerificationConfigV2 memory config) public pure returns (bytes32) {
return sha256(abi.encode(config));
}
// ====================================================
// Internal Functions
// ====================================================
/**
* @notice Executes the complete verification flow.
* @dev Processes user context data, retrieves verification config, performs basic verification,
* executes custom verification logic, and formats the output.
* @param header The decoded hub input header containing verification parameters.
* @param proofData The raw proof data to be decoded and verified.
* @param userContextData The user-provided context data.
* @return output The formatted verification output.
* @return destChainId The destination chain identifier.
* @return userDataToPass The remaining user data to pass through.
*/
function _executeVerificationFlow(
SelfStructs.HubInputHeader memory header,
bytes memory proofData,
bytes calldata userContextData
)
internal
returns (
bytes memory output,
uint256 destChainId,
bytes memory userDataToPass,
bytes32 configId,
uint256 userIdentifier
)
{
bytes calldata remainingData;
{
(configId, destChainId, userIdentifier, remainingData) = _decodeUserContextData(userContextData);
}
{
bytes memory config = _getVerificationConfigById(configId);
bytes memory proofOutput = _basicVerification(
header,
_decodeVcAndDiscloseProof(proofData),
userContextData,
userIdentifier
);
SelfStructs.GenericDiscloseOutputV2 memory genericDiscloseOutput = CustomVerifier.customVerify(
header.attestationId,
config,
proofOutput
);
output = _formatVerificationOutput(header.contractVersion, genericDiscloseOutput);
}
userDataToPass = remainingData;
}
/**
* @notice Handles verification result based on destination chain.
* @dev Routes the verification result to the appropriate handler based on whether
* the destination is the current chain or requires cross-chain bridging.
* @param destChainId The destination chain identifier.
* @param output The verification output data.
* @param userDataToPass The user data to pass to the result handler.
*/
function _handleVerificationResult(uint256 destChainId, bytes memory output, bytes memory userDataToPass) internal {
if (destChainId == block.chainid) {
ISelfVerificationRoot(msg.sender).onVerificationSuccess(output, userDataToPass);
} else {
// Call external bridge
// _handleBridge()
revert CrossChainIsNotSupportedYet();
}
}
/**
* @notice Unified basic verification function for both passport and ID card proofs.
* @dev Performs four core verification steps: scopeCheck, rootCheck, currentDateCheck, groth16 proof verification
* @param header The hub input header containing scope and attestation information
* @param vcAndDiscloseProof The VC and Disclose proof data
* @param userContextData The user context data for validation
* @param userIdentifier The user identifier for proof validation
* @return output The verification result encoded as bytes (PassportOutput or EuIdOutput)
*/
function _basicVerification(
SelfStructs.HubInputHeader memory header,
GenericProofStruct memory vcAndDiscloseProof,
bytes calldata userContextData,
uint256 userIdentifier
) internal returns (bytes memory output) {
// Scope 1: Basic checks (scope and user identifier)
CircuitConstantsV2.DiscloseIndices memory indices = CircuitConstantsV2.getDiscloseIndices(header.attestationId);
{
_performAttestationIdCheck(header.attestationId, vcAndDiscloseProof, indices);
_performScopeCheck(header.scope, vcAndDiscloseProof, indices);
_performUserIdentifierCheck(userContextData, vcAndDiscloseProof, header.attestationId, indices);
}
// Scope 2: Root, OFAC, and current date checks
{
_performRootCheck(header.attestationId, vcAndDiscloseProof, indices);
_performOfacCheck(header.attestationId, vcAndDiscloseProof, indices);
_performCurrentDateCheck(header.attestationId, vcAndDiscloseProof, indices);
}
// Scope 3: Groth16 proof verification
_performGroth16ProofVerification(header.attestationId, vcAndDiscloseProof);
// Scope 4: Create and return output
{
return _createVerificationOutput(header.attestationId, vcAndDiscloseProof, indices, userIdentifier);
}
}
// ====================================================
// Internal View Functions
// ====================================================
/**
* @notice Gets verification config from V2 storage
* @param configId The configuration identifier
* @return The verification configuration
*/
function getVerificationConfigV2(
bytes32 configId
) public view virtual onlyProxy returns (SelfStructs.VerificationConfigV2 memory) {
IdentityVerificationHubV2Storage storage $v2 = _getIdentityVerificationHubV2Storage();
return $v2._v2VerificationConfigs[configId];
}
/**
* @notice Gets verification config by configId
*/
function _getVerificationConfigById(bytes32 configId) internal view returns (bytes memory config) {
IdentityVerificationHubV2Storage storage $v2 = _getIdentityVerificationHubV2Storage();
SelfStructs.VerificationConfigV2 memory verificationConfig = $v2._v2VerificationConfigs[configId];
config = GenericFormatter.formatV2Config(verificationConfig);
if (generateConfigId(verificationConfig) != configId) {
revert ConfigNotSet();
}
return config;
}
/**
* @notice Verifies the register circuit proof.
* @dev Uses the register circuit verifier specified by registerCircuitVerifierId.
* @param attestationId The attestation ID.
* @param registerCircuitVerifierId The identifier for the register circuit verifier.
* @param registerCircuitProof The register circuit proof data.
*/
function _verifyRegisterProof(
bytes32 attestationId,
uint256 registerCircuitVerifierId,
GenericProofStruct memory registerCircuitProof
) internal view {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
address verifier = $._registerCircuitVerifiers[attestationId][registerCircuitVerifierId];
address registryAddress = $._registries[attestationId];
RegisterProofVerifierLib.verifyRegisterProof(
attestationId,
registerCircuitVerifierId,
registerCircuitProof,
verifier,
registryAddress,
AADHAAR_REGISTRATION_WINDOW
);
}
/**
* @notice Verifies the passport DSC circuit proof.
* @dev Uses the DSC circuit verifier specified by dscCircuitVerifierId.
* @param attestationId The attestation ID.
* @param dscCircuitVerifierId The identifier for the DSC circuit verifier.
* @param dscCircuitProof The DSC circuit proof data.
*/
function _verifyDscProof(
bytes32 attestationId,
uint256 dscCircuitVerifierId,
IDscCircuitVerifier.DscCircuitProof memory dscCircuitProof
) internal view {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
address verifier = $._dscCircuitVerifiers[attestationId][dscCircuitVerifierId];
address registryAddress = $._registries[attestationId];
DscProofVerifierLib.verifyDscProof(
attestationId,
dscCircuitVerifierId,
dscCircuitProof,
verifier,
registryAddress
);
}
/**
* @notice Performs attestationId check
*/
function _performAttestationIdCheck(
bytes32 attestationId,
GenericProofStruct memory vcAndDiscloseProof,
CircuitConstantsV2.DiscloseIndices memory indices
) internal pure {
if (vcAndDiscloseProof.pubSignals[indices.attestationIdIndex] != uint256(attestationId)) {
revert AttestationIdMismatch();
}
}
/**
* @notice Performs scope validation
*/
function _performScopeCheck(
uint256 headerScope,
GenericProofStruct memory vcAndDiscloseProof,
CircuitConstantsV2.DiscloseIndices memory indices
) internal view {
// Get scope from proof using the scope index from indices
uint256 proofScope = vcAndDiscloseProof.pubSignals[indices.scopeIndex];
if (headerScope != proofScope) {
revert ScopeMismatch();
}
}
/**
* @notice Performs identity commitment root verification
*/
function _performRootCheck(
bytes32 attestationId,
GenericProofStruct memory vcAndDiscloseProof,
CircuitConstantsV2.DiscloseIndices memory indices
) internal view {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
address registryAddress = $._registries[attestationId];
RootCheckLib.performRootCheck(attestationId, vcAndDiscloseProof, indices, registryAddress);
}
/**
* @notice Performs OFAC compliance verification
* @dev Validates OFAC root and performs sanctions screening if required.
*/
function _performOfacCheck(
bytes32 attestationId,
GenericProofStruct memory vcAndDiscloseProof,
CircuitConstantsV2.DiscloseIndices memory indices
) internal view {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
address registryAddress = $._registries[attestationId];
OfacCheckLib.performOfacCheck(attestationId, vcAndDiscloseProof, indices, registryAddress);
}
/**
* @notice Performs current date validation with format-aware parsing
* @dev Handles three date formats:
* - E_PASSPORT/EU_ID_CARD: 6 ASCII chars (YYMMDD)
* - KYC: 8 ASCII digits (YYYYMMDD)
* - AADHAAR: 3 numeric signals (year, month, day)
* @param attestationId The attestation type to determine date format
* @param vcAndDiscloseProof The proof containing date information
* @param indices Circuit-specific indices for extracting date values
*/
function _performCurrentDateCheck(
bytes32 attestationId,
GenericProofStruct memory vcAndDiscloseProof,
CircuitConstantsV2.DiscloseIndices memory indices
) internal view {
uint256 currentTimestamp;
uint256 startIndex = indices.currentDateIndex;
if (attestationId == AttestationId.E_PASSPORT || attestationId == AttestationId.EU_ID_CARD) {
// E_PASSPORT, EU_ID_CARD: 6 ASCII chars (YYMMDD)
uint256[6] memory dateNum;
unchecked {
for (uint256 i; i < 6; ++i) {
dateNum[i] = vcAndDiscloseProof.pubSignals[startIndex + i];
}
}
currentTimestamp = Formatter.proofDateToUnixTimestamp(dateNum);
} else if (attestationId == AttestationId.KYC) {
// KYC: 8 ASCII digits (YYYYMMDD)
uint256[3] memory dateNum; // [year, month, day]
unchecked {
for (uint256 i; i < 4; ++i)
dateNum[0] = dateNum[0] * 10 + vcAndDiscloseProof.pubSignals[startIndex + i];
for (uint256 i = 4; i < 6; ++i)
dateNum[1] = dateNum[1] * 10 + vcAndDiscloseProof.pubSignals[startIndex + i];
for (uint256 i = 6; i < 8; ++i)
dateNum[2] = dateNum[2] * 10 + vcAndDiscloseProof.pubSignals[startIndex + i];
}
currentTimestamp = Formatter.proofDateToUnixTimestampNumeric(dateNum);
} else {
// AADHAAR: 3 numeric signals [year, month, day]
currentTimestamp = Formatter.proofDateToUnixTimestampNumeric(
[
vcAndDiscloseProof.pubSignals[startIndex],
vcAndDiscloseProof.pubSignals[startIndex + 1],
vcAndDiscloseProof.pubSignals[startIndex + 2]
]
);
}
_validateDateInRange(currentTimestamp);
}
/**
* @notice Validates that a timestamp is within the acceptable range
* @param currentTimestamp The timestamp to validate
*/
function _validateDateInRange(uint256 currentTimestamp) internal view {
// Calculate the timestamp for the start of current date by subtracting the remainder of block.timestamp modulo 1 day
uint256 startOfDay = block.timestamp - (block.timestamp % 1 days);
// Check if timestamp is within range
if (currentTimestamp < startOfDay - 1 days + 1 || currentTimestamp > startOfDay + 1 days - 1) {
revert CurrentDateNotInValidRange();
}
}
/**
* @notice Performs Groth16 proof verification
* @dev Verifies the zero-knowledge proof using the configured verifier contract.
*/
function _performGroth16ProofVerification(
bytes32 attestationId,
GenericProofStruct memory vcAndDiscloseProof
) internal view {
IdentityVerificationHubStorage storage $ = _getIdentityVerificationHubStorage();
ProofVerifierLib.verifyGroth16Proof(attestationId, $._discloseVerifiers[attestationId], vcAndDiscloseProof);
}
// ====================================================
// Internal Pure Functions
// ====================================================
/**
* @notice Decodes the input data to extract the header and proof data.
* @param baseVerificationInput The input data to decode. Format: | 1 byte contractVersion | 31 bytes buffer | 32 bytes scope | 32 bytes attestationId | user defined data |
* @return header The header of the input data.
* @return proofData The proof data of the input data.
*/
function _decodeInput(
bytes calldata baseVerificationInput
) internal pure returns (SelfStructs.HubInputHeader memory header, bytes calldata proofData) {
if (baseVerificationInput.length < 97) {
revert InputTooShort();
}
header.contractVersion = uint8(baseVerificationInput[0]);
header.scope = uint256(bytes32(baseVerificationInput[32:64]));
header.attestationId = bytes32(baseVerificationInput[64:96]);
proofData = baseVerificationInput[96:];
}
/**
* @notice Decodes userContextData to extract configId, destChainId, and userIdentifier
* @param userContextData User-defined data in format: | 32 bytes configId | 32 bytes destChainId | 32 bytes userIdentifier | data |
* @return configId The configuration identifier
* @return destChainId The destination chain identifier
* @return userIdentifier The user identifier
* @return remainingData The remaining data after the first 96 bytes
*/
function _decodeUserContextData(
bytes calldata userContextData
)
internal
pure
returns (bytes32 configId, uint256 destChainId, uint256 userIdentifier, bytes calldata remainingData)
{
if (userContextData.length < 96) {
revert UserContextDataTooShort();
}
configId = bytes32(userContextData[0:32]);
destChainId = uint256(bytes32(userContextData[32:64]));
userIdentifier = uint256(bytes32(userContextData[64:96]));
remainingData = userContextData[96:];
}
/**
* @notice Formats verification output based on contract version.
* @dev Converts the generic disclosure output to the appropriate struct format based on version.
* @param contractVersion The contract version to determine output format.
* @param genericDiscloseOutput The generic disclosure output to format.
* @return output The formatted output as bytes.
*/
function _formatVerificationOutput(
uint256 contractVersion,
SelfStructs.GenericDiscloseOutputV2 memory genericDiscloseOutput
) internal pure returns (bytes memory output) {
if (contractVersion == 2) {
output = GenericFormatter.toV2Struct(genericDiscloseOutput);
}
}
/**
* @notice Creates verification output based on attestation type.
* @dev Formats proof data into the appropriate output structure for the attestation type.
* @param attestationId The attestation identifier (passport, EU ID card, Aadhaar, or KYC).
* @param vcAndDiscloseProof The VC and Disclose proof data.
* @param indices The circuit-specific indices for extracting proof values.
* @param userIdentifier The user identifier to include in the output.
* @return The encoded verification output.
*/
function _createVerificationOutput(
bytes32 attestationId,
GenericProofStruct memory vcAndDiscloseProof,
CircuitConstantsV2.DiscloseIndices memory indices,
uint256 userIdentifier
) internal pure returns (bytes memory) {
if (attestationId == AttestationId.E_PASSPORT) {
return OutputFormatterLib.createPassportOutput(vcAndDiscloseProof, indices, attestationId, userIdentifier);
} else if (attestationId == AttestationId.EU_ID_CARD) {
return OutputFormatterLib.createEuIdOutput(vcAndDiscloseProof, indices, attestationId, userIdentifier);
} else if (attestationId == AttestationId.AADHAAR) {
return OutputFormatterLib.createAadhaarOutput(vcAndDiscloseProof, indices, attestationId, userIdentifier);
} else if (attestationId == AttestationId.KYC) {
return OutputFormatterLib.createKycOutput(vcAndDiscloseProof, indices, attestationId, userIdentifier);
} else {
revert InvalidAttestationId();
}
}
/**
* @notice Decodes VC and Disclose proof from bytes data.
* @dev Simple wrapper around abi.decode for type safety and clarity.
* @param data The encoded proof data.
* @return The decoded VcAndDiscloseProof struct.
*/
function _decodeVcAndDiscloseProof(bytes memory data) internal pure returns (GenericProofStruct memory) {
return abi.decode(data, (GenericProofStruct));
}
/**
* @notice Performs user identifier validation.
* @dev Validates that the user identifier in the proof matches the hash of the user context data.
* Uses SHA256 followed by RIPEMD160 hashing for consistency with circuit implementation.
* @param userContextData The user context data to hash and compare.
* @param vcAndDiscloseProof The VC and Disclose proof containing the user identifier.
* @param attestationId The attestation identifier (used for getting correct indices).
* @param indices The circuit-specific indices for extracting the user identifier from proof.
*/
function _performUserIdentifierCheck(
bytes calldata userContextData,
GenericProofStruct memory vcAndDiscloseProof,
bytes32 attestationId,
CircuitConstantsV2.DiscloseIndices memory indices
) internal pure {
// Get the user identifier index for this attestation type
uint256 proofUserIdentifier = vcAndDiscloseProof.pubSignals[indices.userIdentifierIndex];
bytes memory userContextDataWithoutConfigId = userContextData[32:];
bytes32 sha256Hash = sha256(userContextDataWithoutConfigId);
bytes20 ripemdHash = ripemd160(abi.encodePacked(sha256Hash));
uint256 hashedValue = uint256(uint160(ripemdHash));
if (hashedValue != proofUserIdentifier) {
revert InvalidUserIdentifierInProof();
}
}
}