diff --git a/contracts/contracts/IdentityVerificationHub.sol b/contracts/contracts/IdentityVerificationHub.sol index 8cc1fe957..d3380b9f5 100644 --- a/contracts/contracts/IdentityVerificationHub.sol +++ b/contracts/contracts/IdentityVerificationHub.sol @@ -12,8 +12,8 @@ import {ProxyRoot} from "./upgradeable/ProxyRoot.sol"; contract IdentityVerificationHub is ProxyRoot { /** * @notice Constructs a new IdentityVerificationHub proxy. - * @param _logic The address of the implementation contract containing the hub logic. - * @param _data The initialization data to be executed in the context of the implementation contract. + * @param logic The address of the implementation contract containing the hub logic. + * @param data The initialization data to be executed in the context of the implementation contract. */ - constructor(address _logic, bytes memory _data) ProxyRoot(_logic, _data) {} + constructor(address logic, bytes memory data) ProxyRoot(logic, data) {} } diff --git a/contracts/contracts/abstract/SelfVerificationRoot.sol b/contracts/contracts/abstract/SelfVerificationRoot.sol index 000d8bd24..4f6eb2228 100644 --- a/contracts/contracts/abstract/SelfVerificationRoot.sol +++ b/contracts/contracts/abstract/SelfVerificationRoot.sol @@ -23,7 +23,7 @@ abstract contract SelfVerificationRoot is ISelfVerificationRoot { /// @notice The attestation ID that proofs must match /// @dev Used to validate that submitted proofs is generated with allowed attestation IDs - mapping(uint256 => bool) internal _attestationIds; + mapping(uint256 attestationId => bool attestationIdEnabled) internal _attestationIdToEnabled; /// @notice Configuration settings for the verification process /// @dev Contains settings for age verification, country restrictions, and OFAC checks @@ -69,27 +69,53 @@ abstract contract SelfVerificationRoot is ISelfVerificationRoot { /// @dev Triggered in verifySelfProof when attestation ID validation fails error InvalidAttestationId(); + // ==================================================== + // Events + // ==================================================== + + /// @notice Emitted when the verification configuration is updated + event VerificationConfigUpdated(ISelfVerificationRoot.VerificationConfig indexed verificationConfig); + + /// @notice Emitted when the verification is successful + event VerificationSuccess(uint256[3] revealedDataPacked, uint256 indexed userIdentifier, uint256 indexed nullifier); + + /// @notice Emitted when the scope is updated + event ScopeUpdated(uint256 indexed newScope); + + /// @notice Emitted when a new attestation ID is added + event AttestationIdAdded(uint256 indexed attestationId); + + /// @notice Emitted when an attestation ID is removed + event AttestationIdRemoved(uint256 indexed attestationId); + /** * @notice Initializes the SelfVerificationRoot contract. - * @param identityVerificationHub The address of the Identity Verification Hub. - * @param scope The expected proof scope for user registration. + * @param identityVerificationHubAddress The address of the Identity Verification Hub. + * @param scopeValue The expected proof scope for user registration. * @param attestationIds The expected attestation identifiers required in proofs. */ - constructor(address identityVerificationHub, uint256 scope, uint256[] memory attestationIds) { - _identityVerificationHub = IIdentityVerificationHubV1(identityVerificationHub); - _scope = scope; - for (uint256 i = 0; i < attestationIds.length; i++) { - _attestationIds[attestationIds[i]] = true; + constructor(address identityVerificationHubAddress, uint256 scopeValue, uint256[] memory attestationIds) { + _identityVerificationHub = IIdentityVerificationHubV1(identityVerificationHubAddress); + _scope = scopeValue; + + // Cache array length for gas optimization + uint256 length = attestationIds.length; + for (uint256 i; i < length; ) { + _attestationIdToEnabled[attestationIds[i]] = true; + unchecked { + ++i; + } } } /** * @notice Updates the verification configuration * @dev Used to set or update verification parameters after contract deployment - * @param verificationConfig The new verification configuration to apply + * @param newVerificationConfig The new verification configuration to apply */ - function _setVerificationConfig(ISelfVerificationRoot.VerificationConfig memory verificationConfig) internal { - _verificationConfig = verificationConfig; + function _setVerificationConfig(ISelfVerificationRoot.VerificationConfig memory newVerificationConfig) internal { + _verificationConfig = newVerificationConfig; + emit VerificationConfigUpdated(newVerificationConfig); } /** @@ -108,6 +134,7 @@ abstract contract SelfVerificationRoot is ISelfVerificationRoot { */ function _setScope(uint256 newScope) internal { _scope = newScope; + emit ScopeUpdated(newScope); } /** @@ -116,7 +143,8 @@ abstract contract SelfVerificationRoot is ISelfVerificationRoot { * @param attestationId The attestation ID to add */ function _addAttestationId(uint256 attestationId) internal { - _attestationIds[attestationId] = true; + _attestationIdToEnabled[attestationId] = true; + emit AttestationIdAdded(attestationId); } /** @@ -125,7 +153,8 @@ abstract contract SelfVerificationRoot is ISelfVerificationRoot { * @param attestationId The attestation ID to remove */ function _removeAttestationId(uint256 attestationId) internal { - _attestationIds[attestationId] = false; + _attestationIdToEnabled[attestationId] = false; + emit AttestationIdRemoved(attestationId); } /** @@ -134,8 +163,8 @@ abstract contract SelfVerificationRoot is ISelfVerificationRoot { * @param pubSignals The proof's public signals * @return revealedDataPacked Array of the three packed revealed data values */ - function getRevealedDataPacked( - uint256[21] memory pubSignals + function _getRevealedDataPacked( + uint256[21] calldata pubSignals ) internal pure returns (uint256[3] memory revealedDataPacked) { revealedDataPacked[0] = pubSignals[REVEALED_DATA_PACKED_INDEX]; revealedDataPacked[1] = pubSignals[REVEALED_DATA_PACKED_INDEX + 1]; @@ -148,22 +177,28 @@ abstract contract SelfVerificationRoot is ISelfVerificationRoot { * @dev Validates scope and attestation ID before performing verification through the identity hub * @param proof The proof data for verification and disclosure */ - function verifySelfProof(ISelfVerificationRoot.DiscloseCircuitProof memory proof) public virtual { - if (_scope != proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_SCOPE_INDEX]) { + function verifySelfProof(ISelfVerificationRoot.DiscloseCircuitProof calldata proof) public { + // Cache storage reads for gas optimization + uint256 cachedScope = _scope; + + if (cachedScope != proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_SCOPE_INDEX]) { revert InvalidScope(); } - if (!_attestationIds[proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_ATTESTATION_ID_INDEX]]) { + if (!_attestationIdToEnabled[proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_ATTESTATION_ID_INDEX]]) { revert InvalidAttestationId(); } + // Cache verification config to avoid multiple storage reads + ISelfVerificationRoot.VerificationConfig memory config = _verificationConfig; + _identityVerificationHub.verifyVcAndDisclose( IIdentityVerificationHubV1.VcAndDiscloseHubProof({ - olderThanEnabled: _verificationConfig.olderThanEnabled, - olderThan: _verificationConfig.olderThan, - forbiddenCountriesEnabled: _verificationConfig.forbiddenCountriesEnabled, - forbiddenCountriesListPacked: _verificationConfig.forbiddenCountriesListPacked, - ofacEnabled: _verificationConfig.ofacEnabled, + olderThanEnabled: config.olderThanEnabled, + olderThan: config.olderThan, + forbiddenCountriesEnabled: config.forbiddenCountriesEnabled, + forbiddenCountriesListPacked: config.forbiddenCountriesListPacked, + ofacEnabled: config.ofacEnabled, vcAndDiscloseProof: IVcAndDiscloseCircuitVerifier.VcAndDiscloseProof({ a: proof.a, b: proof.b, @@ -172,5 +207,25 @@ abstract contract SelfVerificationRoot is ISelfVerificationRoot { }) }) ); + + uint256[3] memory revealedDataPacked = _getRevealedDataPacked(proof.pubSignals); + uint256 userIdentifier = proof.pubSignals[USER_IDENTIFIER_INDEX]; + uint256 nullifier = proof.pubSignals[NULLIFIER_INDEX]; + + emit VerificationSuccess(revealedDataPacked, userIdentifier, nullifier); + onBasicVerificationSuccess(revealedDataPacked, userIdentifier, nullifier); } + + /** + * @notice Hook called after successful verification + * @dev Virtual function to be overridden by derived contracts for custom business logic + * @param revealedDataPacked The packed revealed data from the proof + * @param userIdentifier The user identifier from the proof + * @param nullifier The nullifier from the proof + */ + function onBasicVerificationSuccess( + uint256[3] memory revealedDataPacked, + uint256 userIdentifier, + uint256 nullifier + ) internal virtual; } diff --git a/contracts/contracts/example/Airdrop.sol b/contracts/contracts/example/Airdrop.sol index 7b05ad545..a8974545e 100644 --- a/contracts/contracts/example/Airdrop.sol +++ b/contracts/contracts/example/Airdrop.sol @@ -3,16 +3,18 @@ pragma solidity 0.8.28; import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -import {SelfVerificationRoot} from "../abstract/SelfVerificationRoot.sol"; -import {ISelfVerificationRoot} from "../interfaces/ISelfVerificationRoot.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ISelfVerificationRoot} from "../interfaces/ISelfVerificationRoot.sol"; + +import {SelfVerificationRoot} from "../abstract/SelfVerificationRoot.sol"; + /** * @title Airdrop (Experimental) * @notice This contract manages an airdrop campaign by verifying user registrations with zero‐knowledge proofs * and distributing ERC20 tokens. It is provided for testing and demonstration purposes only. * **WARNING:** This contract has not been audited and is NOT intended for production use. - * @dev Inherits from PassportAirdropRoot for registration logic and Ownable for administrative control. + * @dev Inherits from SelfVerificationRoot for registration logic and Ownable for administrative control. */ contract Airdrop is SelfVerificationRoot, Ownable { using SafeERC20 for IERC20; @@ -32,8 +34,11 @@ contract Airdrop is SelfVerificationRoot, Ownable { /// @notice Indicates whether the claim phase is active. bool public isClaimOpen; - mapping(uint256 => uint256) internal _nullifiers; - mapping(uint256 => bool) internal _registeredUserIdentifiers; + /// @notice Maps nullifiers to user identifiers for registration tracking + mapping(uint256 nullifier => uint256 userIdentifier) internal _nullifierToUserIdentifier; + + /// @notice Maps user identifiers to registration status + mapping(uint256 userIdentifier => bool registered) internal _registeredUserIdentifiers; // ==================================================== // Errors @@ -51,9 +56,12 @@ contract Airdrop is SelfVerificationRoot, Ownable { error RegistrationNotClosed(); /// @notice Reverts when a claim is attempted while claiming is not enabled. error ClaimNotOpen(); - - error RegisteredNullifier(); + /// @notice Reverts when an invalid user identifier is provided. error InvalidUserIdentifier(); + /// @notice Reverts when a user identifier has already been registered + error UserIdentifierAlreadyRegistered(); + /// @notice Reverts when a nullifier has already been registered + error RegisteredNullifier(); // ==================================================== // Events @@ -73,13 +81,11 @@ contract Airdrop is SelfVerificationRoot, Ownable { /// @notice Emitted when the claim phase is closed. event ClaimClose(); + /// @notice Emitted when a user identifier is registered. event UserIdentifierRegistered(uint256 indexed registeredUserIdentifier, uint256 indexed nullifier); - /// @notice Emitted when the scope is updated. - event ScopeUpdated(uint256 newScope); - /// @notice Emitted when a new attestation ID is added. - event AttestationIdAdded(uint256 attestationId); - /// @notice Emitted when an attestation ID is removed. - event AttestationIdRemoved(uint256 attestationId); + + /// @notice Emitted when the Merkle root is updated. + event MerkleRootUpdated(bytes32 newMerkleRoot); // ==================================================== // Constructor @@ -89,18 +95,18 @@ contract Airdrop is SelfVerificationRoot, Ownable { * @notice Constructor for the experimental Airdrop contract. * @dev Initializes the airdrop parameters, zero-knowledge verification configuration, * and sets the ERC20 token to be distributed. - * @param _identityVerificationHub The address of the Identity Verification Hub. - * @param _scope The expected proof scope for user registration. - * @param _attestationIds The expected attestation identifiers required in proofs. - * @param _token The address of the ERC20 token for airdrop. + * @param identityVerificationHubAddress The address of the Identity Verification Hub. + * @param scopeValue The expected proof scope for user registration. + * @param attestationIds The expected attestation identifiers required in proofs. + * @param tokenAddress The address of the ERC20 token for airdrop. */ constructor( - address _identityVerificationHub, - uint256 _scope, - uint256[] memory _attestationIds, - address _token - ) SelfVerificationRoot(_identityVerificationHub, _scope, _attestationIds) Ownable(_msgSender()) { - token = IERC20(_token); + address identityVerificationHubAddress, + uint256 scopeValue, + uint256[] memory attestationIds, + address tokenAddress + ) SelfVerificationRoot(identityVerificationHubAddress, scopeValue, attestationIds) Ownable(_msgSender()) { + token = IERC20(tokenAddress); } // ==================================================== @@ -110,10 +116,11 @@ contract Airdrop is SelfVerificationRoot, Ownable { /** * @notice Sets the Merkle root for claim validation. * @dev Only callable by the contract owner. - * @param _merkleRoot The new Merkle root. + * @param newMerkleRoot The new Merkle root. */ - function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { - merkleRoot = _merkleRoot; + function setMerkleRoot(bytes32 newMerkleRoot) external onlyOwner { + merkleRoot = newMerkleRoot; + emit MerkleRootUpdated(newMerkleRoot); } /** @@ -193,32 +200,6 @@ contract Airdrop is SelfVerificationRoot, Ownable { emit ClaimClose(); } - /** - * @notice Registers a user's address by verifying a provided zero-knowledge proof. - * @dev Reverts if the registration phase is not open. - * @param proof The VC and Disclose proof data used to verify and register the user. - */ - function verifySelfProof(ISelfVerificationRoot.DiscloseCircuitProof memory proof) public override { - if (!isRegistrationOpen) { - revert RegistrationNotOpen(); - } - - if (_nullifiers[proof.pubSignals[NULLIFIER_INDEX]] != 0) { - revert RegisteredNullifier(); - } - - if (proof.pubSignals[USER_IDENTIFIER_INDEX] == 0) { - revert InvalidUserIdentifier(); - } - - super.verifySelfProof(proof); - - _nullifiers[proof.pubSignals[NULLIFIER_INDEX]] = proof.pubSignals[USER_IDENTIFIER_INDEX]; - _registeredUserIdentifiers[proof.pubSignals[USER_IDENTIFIER_INDEX]] = true; - - emit UserIdentifierRegistered(proof.pubSignals[USER_IDENTIFIER_INDEX], proof.pubSignals[NULLIFIER_INDEX]); - } - /** * @notice Retrieves the expected proof scope. * @return The scope value used for registration verification. @@ -233,16 +214,7 @@ contract Airdrop is SelfVerificationRoot, Ownable { * @return True if the attestation ID is allowed, false otherwise. */ function isAttestationIdAllowed(uint256 attestationId) external view returns (bool) { - return _attestationIds[attestationId]; - } - - /** - * @notice Retrieves the stored nullifier for a given key. - * @param nullifier The nullifier to query. - * @return The user identifier associated with the nullifier. - */ - function getNullifier(uint256 nullifier) external view returns (uint256) { - return _nullifiers[nullifier]; + return _attestationIdToEnabled[attestationId]; } /** @@ -290,11 +262,53 @@ contract Airdrop is SelfVerificationRoot, Ownable { // Mark as claimed and transfer tokens. _setClaimed(); - IERC20(token).safeTransfer(msg.sender, amount); + token.safeTransfer(msg.sender, amount); emit Claimed(index, msg.sender, amount); } + // ==================================================== + // Override Functions from SelfVerificationRoot + // ==================================================== + + /** + * @notice Hook called after successful verification - handles user registration + * @dev Validates registration conditions and registers the user + * @param userIdentifier The user identifier from the proof + * @param nullifier The nullifier from the proof + */ + function onBasicVerificationSuccess( + uint256[3] memory /* revealedDataPacked */, + uint256 userIdentifier, + uint256 nullifier + ) internal override { + // Check if registration is open + if (!isRegistrationOpen) { + revert RegistrationNotOpen(); + } + + // Check if nullifier has already been registered + if (_nullifierToUserIdentifier[nullifier] != 0) { + revert RegisteredNullifier(); + } + + // Check if user identifier is valid + if (userIdentifier == 0) { + revert InvalidUserIdentifier(); + } + + // Check if user identifier has already been registered + if (_registeredUserIdentifiers[userIdentifier]) { + revert UserIdentifierAlreadyRegistered(); + } + + _nullifierToUserIdentifier[nullifier] = userIdentifier; + _registeredUserIdentifiers[userIdentifier] = true; + + // Emit registration event + emit UserIdentifierRegistered(userIdentifier, nullifier); + } + // ==================================================== // Internal Functions // ==================================================== diff --git a/contracts/contracts/example/HappyBirthday.sol b/contracts/contracts/example/HappyBirthday.sol new file mode 100644 index 000000000..53ed5620a --- /dev/null +++ b/contracts/contracts/example/HappyBirthday.sol @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import {ISelfVerificationRoot} from "../interfaces/ISelfVerificationRoot.sol"; + +import {SelfCircuitLibrary} from "../libraries/SelfCircuitLibrary.sol"; +import {SelfVerificationRoot} from "../abstract/SelfVerificationRoot.sol"; + +/** + * @title SelfHappyBirthday + * @notice A contract that gives out USDC to users on their birthday + * @dev Uses SelfVerificationRoot to handle verification with nullifier management for birthday claims + */ +contract SelfHappyBirthday is SelfVerificationRoot, Ownable { + using SafeERC20 for IERC20; + + // ==================================================== + // Storage Variables + // ==================================================== + + /// @notice USDC token contract + IERC20 public immutable usdc; + + /// @notice Default: 1 dollar (6 decimals for USDC) + uint256 public claimableAmount = 1e6; + + /// @notice Default: 1 day window around birthday + uint256 public claimableWindow = 1 days; + + /// @notice Tracks users who have claimed to prevent double claims + mapping(uint256 nullifier => bool hasClaimed) public hasClaimed; + + // ==================================================== + // Events + // ==================================================== + + event USDCClaimed(address indexed claimer, uint256 amount); + event ClaimableAmountUpdated(uint256 oldAmount, uint256 newAmount); + event ClaimableWindowUpdated(uint256 oldWindow, uint256 newWindow); + + // ==================================================== + // Errors + // ==================================================== + + error NotWithinBirthdayWindow(); + error AlreadyClaimed(); + + /** + * @notice Initializes the HappyBirthday contract + * @param identityVerificationHubAddress The address of the Identity Verification Hub + * @param scopeValue The expected proof scope for user registration + * @param attestationIds Array of allowed attestation identifiers + * @param token The USDC token address + */ + constructor( + address identityVerificationHubAddress, + uint256 scopeValue, + uint256[] memory attestationIds, + address token + ) SelfVerificationRoot(identityVerificationHubAddress, scopeValue, attestationIds) Ownable(_msgSender()) { + usdc = IERC20(token); + } + + // ==================================================== + // External/Public Functions + // ==================================================== + + /** + * @notice Sets the verification configuration + * @param newVerificationConfig The new verification settings + */ + function setVerificationConfig( + ISelfVerificationRoot.VerificationConfig memory newVerificationConfig + ) external onlyOwner { + _setVerificationConfig(newVerificationConfig); + } + + /** + * @notice Sets the claimable USDC amount + * @param newAmount The new claimable amount + */ + function setClaimableAmount(uint256 newAmount) external onlyOwner { + uint256 oldAmount = claimableAmount; + claimableAmount = newAmount; + emit ClaimableAmountUpdated(oldAmount, newAmount); + } + + /** + * @notice Sets the claimable window around birthdays + * @param newWindow The new claimable window in seconds + */ + function setClaimableWindow(uint256 newWindow) external onlyOwner { + uint256 oldWindow = claimableWindow; + claimableWindow = newWindow; + emit ClaimableWindowUpdated(oldWindow, newWindow); + } + + /** + * @notice Allows the owner to withdraw USDC from the contract + * @param to The address to withdraw to + * @param amount The amount to withdraw + */ + function withdrawUSDC(address to, uint256 amount) external onlyOwner { + usdc.safeTransfer(to, amount); + } + + // ==================================================== + // Override Functions from SelfVerificationRoot + // ==================================================== + + /** + * @notice Hook called after successful verification + * @dev Checks user hasn't claimed, validates birthday window, and transfers USDC if eligible + * @param revealedDataPacked The packed revealed data from the proof + * @param userIdentifier The user identifier from the proof + * @param nullifier The nullifier from the proof + */ + function onBasicVerificationSuccess( + uint256[3] memory revealedDataPacked, + uint256 userIdentifier, + uint256 nullifier + ) internal override { + // Get user address from the proof's user identifier + + // Check if user has already claimed + if (hasClaimed[nullifier]) { + revert AlreadyClaimed(); + } + + // Check if within birthday window + if (_isWithinBirthdayWindow(revealedDataPacked)) { + // Mark user as claimed + hasClaimed[nullifier] = true; + + address recipient = address(uint160(userIdentifier)); + + // Transfer USDC to the user + usdc.safeTransfer(recipient, claimableAmount); + + // Emit success event + emit USDCClaimed(recipient, claimableAmount); + } else { + revert NotWithinBirthdayWindow(); + } + } + + // ==================================================== + // Internal Functions + // ==================================================== + + /** + * @notice Checks if the current date is within the user's birthday window + * @param revealedDataPacked The packed revealed data containing DOB information + * @return isWithinWindow True if within the birthday window + */ + function _isWithinBirthdayWindow(uint256[3] memory revealedDataPacked) internal view returns (bool) { + string memory dob = SelfCircuitLibrary.getDateOfBirth(revealedDataPacked); + + bytes memory dobBytes = bytes(dob); + bytes memory dayBytes = new bytes(2); + bytes memory monthBytes = new bytes(2); + + dayBytes[0] = dobBytes[0]; + dayBytes[1] = dobBytes[1]; + + monthBytes[0] = dobBytes[3]; + monthBytes[1] = dobBytes[4]; + + string memory day = string(dayBytes); + string memory month = string(monthBytes); + string memory dobInThisYear = string(abi.encodePacked("25", month, day)); + + uint256 dobInThisYearTimestamp = SelfCircuitLibrary.dateToTimestamp(dobInThisYear); + + uint256 currentTime = block.timestamp; + uint256 timeDifference; + + if (currentTime > dobInThisYearTimestamp) { + timeDifference = currentTime - dobInThisYearTimestamp; + } else { + timeDifference = dobInThisYearTimestamp - currentTime; + } + + return timeDifference <= claimableWindow; + } +} diff --git a/contracts/contracts/example/SelfPassportERC721.sol b/contracts/contracts/example/SelfPassportERC721.sol index dce8a6684..b7f058003 100644 --- a/contracts/contracts/example/SelfPassportERC721.sol +++ b/contracts/contracts/example/SelfPassportERC721.sol @@ -2,10 +2,12 @@ pragma solidity 0.8.28; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import {SelfVerificationRoot} from "../abstract/SelfVerificationRoot.sol"; -import {ISelfVerificationRoot} from "../interfaces/ISelfVerificationRoot.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +import {ISelfVerificationRoot} from "../interfaces/ISelfVerificationRoot.sol"; + import {SelfCircuitLibrary} from "../libraries/SelfCircuitLibrary.sol"; +import {SelfVerificationRoot} from "../abstract/SelfVerificationRoot.sol"; /** * @title SelfPassportERC721 @@ -23,10 +25,10 @@ contract SelfPassportERC721 is SelfVerificationRoot, ERC721, Ownable { uint256 private _tokenIdCounter; /// @notice Mapping from token ID to passport attributes - mapping(uint256 => SelfCircuitLibrary.PassportData) private _passportAttributes; + mapping(uint256 tokenId => SelfCircuitLibrary.PassportData passportAttributes) private _passportAttributes; - /// @notice Mapping to track used nullifiers - mapping(uint256 => bool) private _usedNullifiers; + /// @notice Mapping to track minted user identifiers to prevent double minting + mapping(uint256 userIdentifier => bool minted) private _mintedUserIdentifiers; // ==================================================== // Events @@ -34,21 +36,11 @@ contract SelfPassportERC721 is SelfVerificationRoot, ERC721, Ownable { event PassportNFTMinted(uint256 indexed tokenId, address indexed owner, SelfCircuitLibrary.PassportData attributes); - /// @notice Emitted when the scope is updated - event ScopeUpdated(uint256 newScope); - - /// @notice Emitted when a new attestation ID is added - event AttestationIdAdded(uint256 attestationId); - - /// @notice Emitted when an attestation ID is removed - event AttestationIdRemoved(uint256 attestationId); - // ==================================================== // Errors // ==================================================== - error NullifierAlreadyUsed(); - error RegistrationNotOpen(); + error UserIdentifierAlreadyMinted(); error InvalidUserIdentifier(); // ==================================================== @@ -57,19 +49,23 @@ contract SelfPassportERC721 is SelfVerificationRoot, ERC721, Ownable { /** * @notice Constructor for the SelfPassportERC721 contract - * @param identityVerificationHub The address of the Identity Verification Hub - * @param scope The expected proof scope for user registration - * @param attestationIds The expected attestation identifiers required in proofs + * @param identityVerificationHubAddress The address of the Identity Verification Hub + * @param scopeValue The expected proof scope for user registration + * @param attestationIdsList The expected attestation identifiers required in proofs * @param name The name of the NFT collection * @param symbol The symbol of the NFT collection */ constructor( - address identityVerificationHub, - uint256 scope, - uint256[] memory attestationIds, + address identityVerificationHubAddress, + uint256 scopeValue, + uint256[] memory attestationIdsList, string memory name, string memory symbol - ) SelfVerificationRoot(identityVerificationHub, scope, attestationIds) ERC721(name, symbol) Ownable(_msgSender()) {} + ) + SelfVerificationRoot(identityVerificationHubAddress, scopeValue, attestationIdsList) + ERC721(name, symbol) + Ownable(_msgSender()) + {} // ==================================================== // External/Public Functions @@ -82,7 +78,6 @@ contract SelfPassportERC721 is SelfVerificationRoot, ERC721, Ownable { */ function setScope(uint256 newScope) external onlyOwner { _setScope(newScope); - emit ScopeUpdated(newScope); } /** @@ -92,7 +87,6 @@ contract SelfPassportERC721 is SelfVerificationRoot, ERC721, Ownable { */ function addAttestationId(uint256 attestationId) external onlyOwner { _addAttestationId(attestationId); - emit AttestationIdAdded(attestationId); } /** @@ -102,49 +96,17 @@ contract SelfPassportERC721 is SelfVerificationRoot, ERC721, Ownable { */ function removeAttestationId(uint256 attestationId) external onlyOwner { _removeAttestationId(attestationId); - emit AttestationIdRemoved(attestationId); } /** * @notice Updates the verification configuration * @dev Only callable by the contract owner - * @param verificationConfig The new verification configuration + * @param newVerificationConfig The new verification configuration */ function setVerificationConfig( - ISelfVerificationRoot.VerificationConfig memory verificationConfig + ISelfVerificationRoot.VerificationConfig memory newVerificationConfig ) external onlyOwner { - _setVerificationConfig(verificationConfig); - } - - /** - * @notice Verifies a self-proof and mints an NFT with passport attributes - * @param proof The VC and Disclose proof data used to verify and register the user - */ - function verifySelfProof(ISelfVerificationRoot.DiscloseCircuitProof memory proof) public override { - if (_usedNullifiers[proof.pubSignals[NULLIFIER_INDEX]]) { - revert NullifierAlreadyUsed(); - } - - if (proof.pubSignals[USER_IDENTIFIER_INDEX] == 0) { - revert InvalidUserIdentifier(); - } - - // Verify the proof using the parent contract's logic - super.verifySelfProof(proof); - - // Extract passport attributes from the revealed data using the utility function - uint256[3] memory revealedDataPacked = getRevealedDataPacked(proof.pubSignals); - - // Extract passport data using SelfCircuitLibrary - SelfCircuitLibrary.PassportData memory attributes = SelfCircuitLibrary.extractPassportData(revealedDataPacked); - - // Mint NFT - uint256 tokenId = _tokenIdCounter++; - _mint(msg.sender, tokenId); - _passportAttributes[tokenId] = attributes; - _usedNullifiers[proof.pubSignals[NULLIFIER_INDEX]] = true; - - emit PassportNFTMinted(tokenId, msg.sender, attributes); + _setVerificationConfig(newVerificationConfig); } /** @@ -158,12 +120,12 @@ contract SelfPassportERC721 is SelfVerificationRoot, ERC721, Ownable { } /** - * @notice Check if a nullifier has been used - * @param nullifier The nullifier to check - * @return True if the nullifier has been used, false otherwise + * @notice Check if a user identifier has already minted an NFT + * @param userIdentifier The user identifier to check + * @return True if the user identifier has already minted, false otherwise */ - function isNullifierUsed(uint256 nullifier) external view returns (bool) { - return _usedNullifiers[nullifier]; + function isUserIdentifierMinted(uint256 userIdentifier) external view returns (bool) { + return _mintedUserIdentifiers[userIdentifier]; } /** @@ -172,7 +134,7 @@ contract SelfPassportERC721 is SelfVerificationRoot, ERC721, Ownable { * @return True if the attestation ID is allowed, false otherwise */ function isAttestationIdAllowed(uint256 attestationId) external view returns (bool) { - return _attestationIds[attestationId]; + return _attestationIdToEnabled[attestationId]; } /** @@ -191,6 +153,43 @@ contract SelfPassportERC721 is SelfVerificationRoot, ERC721, Ownable { return _getVerificationConfig(); } + // ==================================================== + // Override Functions from SelfVerificationRoot + // ==================================================== + + /** + * @notice Hook called after successful verification - handles NFT minting + * @dev Validates user identifier and mints passport NFT with extracted attributes + * @param revealedDataPacked The packed revealed data from the proof + * @param userIdentifier The user identifier from the proof + */ + function onBasicVerificationSuccess( + uint256[3] memory revealedDataPacked, + uint256 userIdentifier, + uint256 /* nullifier */ + ) internal override { + // Check if user identifier is valid + if (userIdentifier == 0) { + revert InvalidUserIdentifier(); + } + + // Check if user identifier has already minted an NFT + if (_mintedUserIdentifiers[userIdentifier]) { + revert UserIdentifierAlreadyMinted(); + } + + // Extract passport data using SelfCircuitLibrary + SelfCircuitLibrary.PassportData memory attributes = SelfCircuitLibrary.extractPassportData(revealedDataPacked); + + // Mint NFT + uint256 tokenId = _tokenIdCounter++; + _mint(msg.sender, tokenId); + _passportAttributes[tokenId] = attributes; + _mintedUserIdentifiers[userIdentifier] = true; + + emit PassportNFTMinted(tokenId, msg.sender, attributes); + } + // ==================================================== // Internal Functions // ==================================================== diff --git a/contracts/contracts/interfaces/IDscCircuitVerifier.sol b/contracts/contracts/interfaces/IDscCircuitVerifier.sol index 88f48ea92..76dbc0b4f 100644 --- a/contracts/contracts/interfaces/IDscCircuitVerifier.sol +++ b/contracts/contracts/interfaces/IDscCircuitVerifier.sol @@ -24,16 +24,16 @@ interface IDscCircuitVerifier { /** * @notice Verifies a given DSC circuit zero-knowledge proof. * @dev This function checks the validity of the provided DSC proof parameters. - * @param _pA The 'a' component of the proof. - * @param _pB The 'b' component of the proof. - * @param _pC The 'c' component of the proof. - * @param _pubSignals The public signals associated with the proof. + * @param pA The 'a' component of the proof. + * @param pB The 'b' component of the proof. + * @param pC The 'c' component of the proof. + * @param pubSignals The public signals associated with the proof. * @return A boolean value indicating whether the provided proof is valid (true) or not (false). */ function verifyProof( - uint[2] calldata _pA, - uint[2][2] calldata _pB, - uint[2] calldata _pC, - uint[2] calldata _pubSignals + uint[2] calldata pA, + uint[2][2] calldata pB, + uint[2] calldata pC, + uint[2] calldata pubSignals ) external view returns (bool); } diff --git a/contracts/contracts/interfaces/ISelfVerificationRoot.sol b/contracts/contracts/interfaces/ISelfVerificationRoot.sol index ada96371d..747d5871c 100644 --- a/contracts/contracts/interfaces/ISelfVerificationRoot.sol +++ b/contracts/contracts/interfaces/ISelfVerificationRoot.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.28; -import {IVcAndDiscloseCircuitVerifier} from "./IVcAndDiscloseCircuitVerifier.sol"; - interface ISelfVerificationRoot { struct VerificationConfig { bool olderThanEnabled; diff --git a/contracts/contracts/registry/IdentityRegistry.sol b/contracts/contracts/registry/IdentityRegistry.sol index bf8a0128a..f2e1880e8 100644 --- a/contracts/contracts/registry/IdentityRegistry.sol +++ b/contracts/contracts/registry/IdentityRegistry.sol @@ -12,8 +12,8 @@ import {ProxyRoot} from "../upgradeable/ProxyRoot.sol"; contract IdentityRegistry is ProxyRoot { /** * @notice Creates a new instance of the IdentityRegistry proxy. - * @param _logic The address of the initial implementation contract that contains the registry logic. - * @param _data The initialization data passed to the implementation during deployment. + * @param logic The address of the initial implementation contract that contains the registry logic. + * @param data The initialization data passed to the implementation during deployment. */ - constructor(address _logic, bytes memory _data) ProxyRoot(_logic, _data) {} + constructor(address logic, bytes memory data) ProxyRoot(logic, data) {} } diff --git a/contracts/contracts/registry/IdentityRegistryImplV1.sol b/contracts/contracts/registry/IdentityRegistryImplV1.sol index f452fa36c..afa4e141f 100644 --- a/contracts/contracts/registry/IdentityRegistryImplV1.sol +++ b/contracts/contracts/registry/IdentityRegistryImplV1.sol @@ -180,12 +180,12 @@ contract IdentityRegistryImplV1 is IdentityRegistryStorageV1, IIdentityRegistryV /** * @notice Initializes the registry implementation. * @dev Sets the hub address and initializes the UUPS upgradeable feature. - * @param _hub The address of the identity verification hub. + * @param hubAddress The address of the identity verification hub. */ - function initialize(address _hub) external initializer { + function initialize(address hubAddress) external initializer { __ImplRoot_init(); - _hub = _hub; - emit RegistryInitialized(_hub); + _hub = hubAddress; + emit RegistryInitialized(hubAddress); } // ==================================================== diff --git a/contracts/contracts/upgradeable/ProxyRoot.sol b/contracts/contracts/upgradeable/ProxyRoot.sol index 94de15809..60572e914 100644 --- a/contracts/contracts/upgradeable/ProxyRoot.sol +++ b/contracts/contracts/upgradeable/ProxyRoot.sol @@ -11,8 +11,8 @@ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.s contract ProxyRoot is ERC1967Proxy { /** * @notice Creates a new upgradeable proxy. - * @param _logic The address of the initial implementation contract. - * @param _data The initialization calldata to be passed to the implementation contract. + * @param logic The address of the initial implementation contract. + * @param data The initialization calldata to be passed to the implementation contract. */ - constructor(address _logic, bytes memory _data) ERC1967Proxy(_logic, _data) {} + constructor(address logic, bytes memory data) ERC1967Proxy(logic, data) {} }