diff --git a/contracts/contracts/IdentityVerificationHub.sol b/contracts/contracts/IdentityVerificationHub.sol index f21bffc7d..6422c0bc1 100644 --- a/contracts/contracts/IdentityVerificationHub.sol +++ b/contracts/contracts/IdentityVerificationHub.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; -import "./upgradeable/ProxyRoot.sol"; +import {ProxyRoot} from "./upgradeable/ProxyRoot.sol"; /** * @title IdentityVerificationHub diff --git a/contracts/contracts/IdentityVerificationHubImplV1.sol b/contracts/contracts/IdentityVerificationHubImplV1.sol index 1998fd2f1..73e78b813 100644 --- a/contracts/contracts/IdentityVerificationHubImplV1.sol +++ b/contracts/contracts/IdentityVerificationHubImplV1.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import "./constants/CircuitConstants.sol"; -import "./constants/AttestationId.sol"; -import "./libraries/Formatter.sol"; -import "./libraries/CircuitAttributeHandler.sol"; -import "./interfaces/IIdentityVerificationHubV1.sol"; -import "./interfaces/IIdentityRegistryV1.sol"; -import "./interfaces/IRegisterCircuitVerifier.sol"; -import "./interfaces/IVcAndDiscloseCircuitVerifier.sol"; -import "./interfaces/IDscCircuitVerifier.sol"; -import "./upgradeable/ImplRoot.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {CircuitConstants} from "./constants/CircuitConstants.sol"; +import {AttestationId} from "./constants/AttestationId.sol"; +import {Formatter} from "./libraries/Formatter.sol"; +import {CircuitAttributeHandler} from "./libraries/CircuitAttributeHandler.sol"; +import {IIdentityVerificationHubV1} from "./interfaces/IIdentityVerificationHubV1.sol"; +import {IIdentityRegistryV1} from "./interfaces/IIdentityRegistryV1.sol"; +import {IRegisterCircuitVerifier} from "./interfaces/IRegisterCircuitVerifier.sol"; +import {IVcAndDiscloseCircuitVerifier} from "./interfaces/IVcAndDiscloseCircuitVerifier.sol"; +import {IDscCircuitVerifier} from "./interfaces/IDscCircuitVerifier.sol"; +import {ImplRoot} from "./upgradeable/ImplRoot.sol"; /** * @notice ⚠️ CRITICAL STORAGE LAYOUT WARNING ⚠️ diff --git a/contracts/contracts/abstract/PassportAirdropRoot.sol b/contracts/contracts/abstract/PassportAirdropRoot.sol index a7496351d..4f3a935b9 100644 --- a/contracts/contracts/abstract/PassportAirdropRoot.sol +++ b/contracts/contracts/abstract/PassportAirdropRoot.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; import {IIdentityVerificationHubV1} from "../interfaces/IIdentityVerificationHubV1.sol"; import {IVcAndDiscloseCircuitVerifier} from "../interfaces/IVcAndDiscloseCircuitVerifier.sol"; @@ -63,6 +63,8 @@ abstract contract PassportAirdropRoot is error InvalidScope(); /// @dev Reverts if the identity root timestamp is not valid. error InvalidTimestamp(); + /// @dev Reverts if the user identifier is not valid. + error InvalidUserIdentifier(); /** * @notice Initializes the PassportAirdropRoot contract. @@ -127,6 +129,10 @@ abstract contract PassportAirdropRoot is if (_attestationId != proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_ATTESTATION_ID_INDEX]) { revert InvalidAttestationId(); } + + if (proof.pubSignals[CircuitConstants.VC_AND_DISCLOSE_USER_IDENTIFIER_INDEX] == 0) { + revert InvalidUserIdentifier(); + } IIdentityVerificationHubV1.VcAndDiscloseVerificationResult memory result = _identityVerificationHub.verifyVcAndDisclose( IIdentityVerificationHubV1.VcAndDiscloseHubProof({ diff --git a/contracts/contracts/constants/AttestationId.sol b/contracts/contracts/constants/AttestationId.sol index 7fb363b26..c6f6f239a 100644 --- a/contracts/contracts/constants/AttestationId.sol +++ b/contracts/contracts/constants/AttestationId.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; /** * @title AttestationId Library diff --git a/contracts/contracts/constants/CircuitConstants.sol b/contracts/contracts/constants/CircuitConstants.sol index 54262c6e5..acfb3fd24 100644 --- a/contracts/contracts/constants/CircuitConstants.sol +++ b/contracts/contracts/constants/CircuitConstants.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; /** * @title Circuit Constants Library diff --git a/contracts/contracts/example/Airdrop.sol b/contracts/contracts/example/Airdrop.sol index 2c301d9e8..227fad574 100644 --- a/contracts/contracts/example/Airdrop.sol +++ b/contracts/contracts/example/Airdrop.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +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"; diff --git a/contracts/contracts/interfaces/IDscCircuitVerifier.sol b/contracts/contracts/interfaces/IDscCircuitVerifier.sol index dc4d43e3b..5923f04bc 100644 --- a/contracts/contracts/interfaces/IDscCircuitVerifier.sol +++ b/contracts/contracts/interfaces/IDscCircuitVerifier.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; /** * @title IDscCircuitVerifier diff --git a/contracts/contracts/interfaces/IIdentityRegistryV1.sol b/contracts/contracts/interfaces/IIdentityRegistryV1.sol index f00801955..92d47dadf 100644 --- a/contracts/contracts/interfaces/IIdentityRegistryV1.sol +++ b/contracts/contracts/interfaces/IIdentityRegistryV1.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; /** * @title IIdentityRegistryV1 diff --git a/contracts/contracts/interfaces/IIdentityVerificationHubV1.sol b/contracts/contracts/interfaces/IIdentityVerificationHubV1.sol index e5c345b69..1b1051dcb 100644 --- a/contracts/contracts/interfaces/IIdentityVerificationHubV1.sol +++ b/contracts/contracts/interfaces/IIdentityVerificationHubV1.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; -import "./IRegisterCircuitVerifier.sol"; -import "./IDscCircuitVerifier.sol"; -import "./IVcAndDiscloseCircuitVerifier.sol"; -import "../constants/CircuitConstants.sol"; +import {IRegisterCircuitVerifier} from "./IRegisterCircuitVerifier.sol"; +import {IDscCircuitVerifier} from "./IDscCircuitVerifier.sol"; +import {IVcAndDiscloseCircuitVerifier} from "./IVcAndDiscloseCircuitVerifier.sol"; +import {CircuitConstants} from "../constants/CircuitConstants.sol"; /** * @title IIdentityVerificationHubV1 diff --git a/contracts/contracts/interfaces/IPassportAirdropRoot.sol b/contracts/contracts/interfaces/IPassportAirdropRoot.sol index 70b85fb9d..f37d0cf98 100644 --- a/contracts/contracts/interfaces/IPassportAirdropRoot.sol +++ b/contracts/contracts/interfaces/IPassportAirdropRoot.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; /** * @title IPassportAirdropRoot diff --git a/contracts/contracts/interfaces/IRegisterCircuitVerifier.sol b/contracts/contracts/interfaces/IRegisterCircuitVerifier.sol index f8e4e692c..c8422609a 100644 --- a/contracts/contracts/interfaces/IRegisterCircuitVerifier.sol +++ b/contracts/contracts/interfaces/IRegisterCircuitVerifier.sol @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; - +pragma solidity 0.8.28; /** * @title IRegisterCircuitVerifier * @notice Interface for verifying register circuit proofs. diff --git a/contracts/contracts/interfaces/IVcAndDiscloseCircuitVerifier.sol b/contracts/contracts/interfaces/IVcAndDiscloseCircuitVerifier.sol index 34567a7a7..0c2e8a283 100644 --- a/contracts/contracts/interfaces/IVcAndDiscloseCircuitVerifier.sol +++ b/contracts/contracts/interfaces/IVcAndDiscloseCircuitVerifier.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; /** * @title IVcAndDiscloseCircuitVerifier diff --git a/contracts/contracts/libraries/CircuitAttributeHandler.sol b/contracts/contracts/libraries/CircuitAttributeHandler.sol index e1cd95b01..d66afcf17 100644 --- a/contracts/contracts/libraries/CircuitAttributeHandler.sol +++ b/contracts/contracts/libraries/CircuitAttributeHandler.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.28; -import "../interfaces/IVcAndDiscloseCircuitVerifier.sol"; -import "../constants/CircuitConstants.sol"; -import "./Formatter.sol"; +import {IVcAndDiscloseCircuitVerifier} from "../interfaces/IVcAndDiscloseCircuitVerifier.sol"; +import {CircuitConstants} from "../constants/CircuitConstants.sol"; +import {Formatter} from "./Formatter.sol"; /** * @title CircuitAttributeHandler Library diff --git a/contracts/contracts/libraries/Formatter.sol b/contracts/contracts/libraries/Formatter.sol index 0686fd6e5..964e15673 100644 --- a/contracts/contracts/libraries/Formatter.sol +++ b/contracts/contracts/libraries/Formatter.sol @@ -8,8 +8,14 @@ pragma solidity ^0.8.28; library Formatter { error InvalidDateLength(); error InvalidAsciiCode(); + error InvalidYearRange(); + error InvalidMonthRange(); + error InvalidDayRange(); + error InvalidFieldElement(); + error InvalidDateDigit(); uint256 constant MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH = 10; + uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617; /** * @notice Formats a full name string into first name(s) and last name. @@ -67,11 +73,25 @@ library Formatter { function formatDate( string memory date ) internal pure returns (string memory) { - // Ensure the date string is the correct length. - if (bytes(date).length != 6) { + bytes memory dateBytes = bytes(date); + if (dateBytes.length != 6) { revert InvalidDateLength(); } + if (dateBytes[2] > '1' || (dateBytes[2] == '1' && dateBytes[3] > '2')) { + revert InvalidMonthRange(); + } + + if (dateBytes[4] > '3' || (dateBytes[4] == '3' && dateBytes[5] > '1')) { + revert InvalidDayRange(); + } + + for (uint i = 0; i < 6; i++) { + if (dateBytes[i] < '0' || dateBytes[i] > '9') { + revert InvalidAsciiCode(); + } + } + string memory year = substring(date, 0, 2); string memory month = substring(date, 2, 4); string memory day = substring(date, 4, 6); @@ -102,6 +122,13 @@ library Formatter { function fieldElementsToBytes( uint256[3] memory publicSignals ) internal pure returns (bytes memory) { + if ( + publicSignals[0] >= SNARK_SCALAR_FIELD || + publicSignals[1] >= SNARK_SCALAR_FIELD || + publicSignals[2] >= SNARK_SCALAR_FIELD + ) { + revert InvalidFieldElement(); + } uint8[3] memory bytesCount = [31, 31, 29]; bytes memory bytesArray = new bytes(91); @@ -132,6 +159,10 @@ library Formatter { string[MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH] memory forbiddenCountries ) { + if (publicSignal >= SNARK_SCALAR_FIELD) { + revert InvalidFieldElement(); + } + for (uint256 j = 0; j < MAX_FORBIDDEN_COUNTRIES_LIST_LENGTH; j++) { uint256 byteIndex = j * 3; @@ -155,6 +186,11 @@ library Formatter { function proofDateToUnixTimestamp( uint256[6] memory dateNum ) internal pure returns (uint256) { + for (uint256 i = 0; i < 6; i++) { + if (dateNum[i] > 9) { + revert InvalidDateDigit(); + } + } string memory date = ""; for (uint256 i = 0; i < 6; i++) { date = string( @@ -176,10 +212,25 @@ library Formatter { function dateToUnixTimestamp( string memory date ) internal pure returns (uint256) { - if (bytes(date).length != 6) { + bytes memory dateBytes = bytes(date); + if (dateBytes.length != 6) { revert InvalidDateLength(); } + if (dateBytes[2] > '1' || (dateBytes[2] == '1' && dateBytes[3] > '2')) { + revert InvalidMonthRange(); + } + + if (dateBytes[4] > '3' || (dateBytes[4] == '3' && dateBytes[5] > '1')) { + revert InvalidDayRange(); + } + + for (uint i = 0; i < 6; i++) { + if (dateBytes[i] < '0' || dateBytes[i] > '9') { + revert InvalidAsciiCode(); + } + } + uint256 year = parseDatePart(substring(date, 0, 2)) + 2000; uint256 month = parseDatePart(substring(date, 2, 4)); uint256 day = parseDatePart(substring(date, 4, 6)); @@ -225,7 +276,10 @@ library Formatter { uint digit; uint result; for (uint i = 0; i < tempEmptyStringTest.length; i++) { - digit = uint(uint8(tempEmptyStringTest[i])) - 48; // '0' is 48 in ASCII + if (uint8(tempEmptyStringTest[i]) < 48 || uint8(tempEmptyStringTest[i]) > 57) { + revert InvalidAsciiCode(); + } + digit = uint8(tempEmptyStringTest[i]) - 48; result = result * 10 + digit; } return result; @@ -247,6 +301,14 @@ library Formatter { ) internal pure returns (uint timestamp) { uint16 i; + if (year < 1970 || year > 2100) { + revert InvalidYearRange(); + } + + if (month < 1 || month > 12) { + revert InvalidMonthRange(); + } + // Year. for (i = 1970; i < year; i++) { if (isLeapYear(i)) { @@ -275,6 +337,10 @@ library Formatter { monthDayCounts[10] = 30; monthDayCounts[11] = 31; + if (day < 1 || day > monthDayCounts[month - 1]) { + revert InvalidDayRange(); + } + for (i = 1; i < month; i++) { timestamp += monthDayCounts[i - 1] * 1 days; } @@ -291,6 +357,10 @@ library Formatter { * @return True if the year is a leap year, otherwise false. */ function isLeapYear(uint256 year) internal pure returns (bool) { + if (year < 1970 || year > 2100) { + revert InvalidYearRange(); + } + if (year % 4 != 0) { return false; } else if (year % 100 != 0) { diff --git a/contracts/contracts/registry/IdentityRegistry.sol b/contracts/contracts/registry/IdentityRegistry.sol index d13b9c6d2..2681ea55a 100644 --- a/contracts/contracts/registry/IdentityRegistry.sol +++ b/contracts/contracts/registry/IdentityRegistry.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; -import "../upgradeable/ProxyRoot.sol"; +import {ProxyRoot} from "../upgradeable/ProxyRoot.sol"; /** * @title IdentityRegistry diff --git a/contracts/contracts/registry/IdentityRegistryImplV1.sol b/contracts/contracts/registry/IdentityRegistryImplV1.sol index 6b10477cf..fe4a66852 100644 --- a/contracts/contracts/registry/IdentityRegistryImplV1.sol +++ b/contracts/contracts/registry/IdentityRegistryImplV1.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import { InternalLeanIMT, LeanIMTData } from "@zk-kit/imt.sol/internal/InternalLeanIMT.sol"; -import "../interfaces/IIdentityRegistryV1.sol"; -import "../interfaces/IIdentityVerificationHubV1.sol"; -import "../upgradeable/ImplRoot.sol"; +import {IIdentityRegistryV1} from "../interfaces/IIdentityRegistryV1.sol"; +import {IIdentityVerificationHubV1} from "../interfaces/IIdentityVerificationHubV1.sol"; +import {ImplRoot} from "../upgradeable/ImplRoot.sol"; /** * @notice ⚠️ CRITICAL STORAGE LAYOUT WARNING ⚠️ * ============================================= diff --git a/contracts/contracts/sdk/VerifyAll.sol b/contracts/contracts/sdk/VerifyAll.sol index 2e0014f25..fff3fada2 100644 --- a/contracts/contracts/sdk/VerifyAll.sol +++ b/contracts/contracts/sdk/VerifyAll.sol @@ -1,16 +1,22 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; import {IIdentityVerificationHubV1} from "../interfaces/IIdentityVerificationHubV1.sol"; import {IIdentityRegistryV1} from "../interfaces/IIdentityRegistryV1.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {CircuitConstants} from "../constants/CircuitConstants.sol"; +/// @title VerifyAll +/// @notice A contract for verifying identity proofs and revealing selected data +/// @dev This contract interacts with IdentityVerificationHub and IdentityRegistry contract VerifyAll is Ownable { IIdentityVerificationHubV1 _hub; IIdentityRegistryV1 _registry; + /// @notice Initializes the contract with hub and registry addresses + /// @param hub The address of the IdentityVerificationHub contract + /// @param registry The address of the IdentityRegistry contract constructor( address hub, address registry @@ -19,6 +25,12 @@ contract VerifyAll is Ownable { _registry = IIdentityRegistryV1(registry); } + /// @notice Verifies identity proof and reveals selected data + /// @param targetRootTimestamp The expected timestamp of the identity commitment root (0 to skip check) + /// @param proof The VC and disclosure proof to verify + /// @param types Array of data types to reveal + /// @return readableData The revealed data in readable format + /// @return success Whether the verification was successful function verifyAll ( uint256 targetRootTimestamp, IIdentityVerificationHubV1.VcAndDiscloseHubProof memory proof, @@ -73,10 +85,16 @@ contract VerifyAll is Ownable { return (readableData, true); } + /// @notice Updates the hub contract address + /// @param hub The new hub contract address + /// @dev Only callable by the contract owner function setHub(address hub) external onlyOwner { _hub = IIdentityVerificationHubV1(hub); } + /// @notice Updates the registry contract address + /// @param registry The new registry contract address + /// @dev Only callable by the contract owner function setRegistry(address registry) external onlyOwner { _registry = IIdentityRegistryV1(registry); } diff --git a/contracts/contracts/tests/airdropToken.sol b/contracts/contracts/tests/airdropToken.sol index 28c170f7c..06dd7b7de 100644 --- a/contracts/contracts/tests/airdropToken.sol +++ b/contracts/contracts/tests/airdropToken.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; diff --git a/contracts/contracts/tests/testCircuitAttributeHandler.sol b/contracts/contracts/tests/testCircuitAttributeHandler.sol index a274ec6e2..1a86876d8 100644 --- a/contracts/contracts/tests/testCircuitAttributeHandler.sol +++ b/contracts/contracts/tests/testCircuitAttributeHandler.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; -import "../libraries/CircuitAttributeHandler.sol"; +import {CircuitAttributeHandler} from "../libraries/CircuitAttributeHandler.sol"; contract TestCircuitAttributeHandler { function testGetIssuingState(bytes memory charcodes) external pure returns (string memory) { diff --git a/contracts/contracts/tests/testFormatter.sol b/contracts/contracts/tests/testFormatter.sol index cd4bdf0f2..e225f69ff 100644 --- a/contracts/contracts/tests/testFormatter.sol +++ b/contracts/contracts/tests/testFormatter.sol @@ -1,30 +1,25 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; -import "../libraries/Formatter.sol"; +import {Formatter} from "../libraries/Formatter.sol"; contract TestFormatter { - // tested function testFormatName(string memory input) external pure returns (string[] memory) { return Formatter.formatName(input); } - // tested function testFormatDate(string memory date) external pure returns (string memory) { return Formatter.formatDate(date); } - // tested function testNumAsciiToUint(uint256 numAscii) external pure returns (uint256) { return Formatter.numAsciiToUint(numAscii); } - // tested function testFieldElementsToBytes(uint256[3] memory publicSignals) external pure returns (bytes memory) { return Formatter.fieldElementsToBytes(publicSignals); } - // tested function testExtractForbiddenCountriesFromPacked(uint256 publicSignal) external pure @@ -37,17 +32,14 @@ contract TestFormatter { return Formatter.proofDateToUnixTimestamp(dateNum); } - // tested function testDateToUnixTimestamp(string memory date) external pure returns (uint256) { return Formatter.dateToUnixTimestamp(date); } - // tested function testSubstring(string memory str, uint startIndex, uint endIndex) external pure returns (string memory) { return Formatter.substring(str, startIndex, endIndex); } - // tested function testParseDatePart(string memory value) external pure returns (uint) { return Formatter.parseDatePart(value); } @@ -56,7 +48,6 @@ contract TestFormatter { return Formatter.toTimestamp(year, month, day); } - // tested function testIsLeapYear(uint256 year) external pure returns (bool) { return Formatter.isLeapYear(year); } diff --git a/contracts/contracts/tests/testImplRoot.sol b/contracts/contracts/tests/testImplRoot.sol index c177d3247..46406cd60 100644 --- a/contracts/contracts/tests/testImplRoot.sol +++ b/contracts/contracts/tests/testImplRoot.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; -import "../../contracts/upgradeable/ImplRoot.sol"; +import {ImplRoot} from "../../contracts/upgradeable/ImplRoot.sol"; contract MockImplRoot is ImplRoot { diff --git a/contracts/contracts/upgradeable/ImplRoot.sol b/contracts/contracts/upgradeable/ImplRoot.sol index 5ec87536f..12c6ebfc6 100644 --- a/contracts/contracts/upgradeable/ImplRoot.sol +++ b/contracts/contracts/upgradeable/ImplRoot.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; -import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; /** * @title ImplRoot diff --git a/contracts/contracts/upgradeable/ProxyRoot.sol b/contracts/contracts/upgradeable/ProxyRoot.sol index d09f426c9..1ca98f2e9 100644 --- a/contracts/contracts/upgradeable/ProxyRoot.sol +++ b/contracts/contracts/upgradeable/ProxyRoot.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.28; +pragma solidity 0.8.28; -import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; /** * @title ProxyRoot diff --git a/contracts/package.json b/contracts/package.json index 08ebd87d6..a581d8a9f 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -33,8 +33,9 @@ "test:coverage:local": "TEST_ENV=local npx hardhat coverage", "test:disclose:local": "TEST_ENV=local npx hardhat test test/integration/vcAndDisclose.test.ts", "test:endtoend:local": "TEST_ENV=local npx hardhat test test/Integration/endToEnd.test.ts", + "test:verifyall:local": "TEST_ENV=local npx hardhat test test/integration/verifyAll.test.ts", "test:example:local": "TEST_ENV=local npx hardhat test test/example/*", - "test:formatter:local": "TEST_ENV=local npx hardhat test test/unit/Formatter.test.ts", + "test:formatter:local": "TEST_ENV=local npx hardhat test test/unit/formatter.test.ts", "test:hub:local": "TEST_ENV=local npx hardhat test test/unit/IdentityVerificationHub.test.ts", "test:integration:local": "TEST_ENV=local npx hardhat test test/integration/*", "test:local": "TEST_ENV=local npx hardhat test", diff --git a/contracts/test/example/airdrop.test.ts b/contracts/test/example/airdrop.test.ts index b56514cab..4217762ca 100644 --- a/contracts/test/example/airdrop.test.ts +++ b/contracts/test/example/airdrop.test.ts @@ -289,6 +289,29 @@ describe("Airdrop", () => { await expect(airdrop.connect(user1).registerAddress(vcAndDiscloseProof)) .to.be.revertedWithCustomError(airdrop, "InvalidAttestationId"); }); + + it("should revert with InvalidUserIdentifier when user identifier is 0", async () => { + const { owner, user1 } = deployedActors; + + vcAndDiscloseProof = await generateVcAndDiscloseProof( + registerSecret, + BigInt(ATTESTATION_ID.E_PASSPORT).toString(), + deployedActors.mockPassport, + "test-airdrop", + new Array(88).fill("1"), + "1", + imt, + "20", + undefined, + undefined, + forbiddenCountriesList, + "0000000000000000000000000000000000000000" + ); + + await airdrop.connect(owner).openRegistration(); + await expect(airdrop.connect(user1).registerAddress(vcAndDiscloseProof)) + .to.be.revertedWithCustomError(airdrop, "InvalidUserIdentifier"); + }); it("should not able to register address when rootTimestamp is different", async () => { const {registry, owner, user1, mockPassport} = deployedActors; diff --git a/contracts/test/integration/verifyAll.test.ts b/contracts/test/integration/verifyAll.test.ts new file mode 100644 index 000000000..c8f8946e5 --- /dev/null +++ b/contracts/test/integration/verifyAll.test.ts @@ -0,0 +1,221 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { deploySystemFixtures } from "../utils/deployment"; +import { DeployedActors } from "../utils/types"; +import { generateRandomFieldElement } from "../utils/utils"; +import { generateCommitment } from "../../../common/src/utils/passports/passport"; +import { ATTESTATION_ID, CIRCUIT_CONSTANTS } from "../utils/constants"; +import { LeanIMT } from "@openpassport/zk-kit-lean-imt"; +import { poseidon2 } from "poseidon-lite"; +import { generateVcAndDiscloseProof } from "../utils/generateProof"; +import { Formatter } from "../utils/formatter"; +import { formatCountriesList, reverseBytes } from "../../../common/src/utils/circuits/formatInputs"; +import { VerifyAll } from "../../typechain-types"; + +describe("VerifyAll", () => { + let deployedActors: DeployedActors; + let verifyAll: VerifyAll; + let snapshotId: string; + let baseVcAndDiscloseProof: any; + let vcAndDiscloseProof: any; + let registerSecret: any; + let imt: any; + let commitment: any; + let nullifier: any; + let forbiddenCountriesList: string[]; + let forbiddenCountriesListPacked: string; + + before(async () => { + deployedActors = await deploySystemFixtures(); + const VerifyAllFactory = await ethers.getContractFactory("VerifyAll"); + verifyAll = await VerifyAllFactory.deploy( + deployedActors.hub.getAddress(), + deployedActors.registry.getAddress() + ); + + registerSecret = generateRandomFieldElement(); + nullifier = generateRandomFieldElement(); + commitment = generateCommitment(registerSecret, ATTESTATION_ID.E_PASSPORT, deployedActors.mockPassport); + + const hashFunction = (a: bigint, b: bigint) => poseidon2([a, b]); + imt = new LeanIMT(hashFunction); + await imt.insert(BigInt(commitment)); + + forbiddenCountriesList = ['AAA', 'ABC', 'CBA']; + forbiddenCountriesListPacked = reverseBytes(Formatter.bytesToHexString(new Uint8Array(formatCountriesList(forbiddenCountriesList)))); + + baseVcAndDiscloseProof = await generateVcAndDiscloseProof( + registerSecret, + BigInt(ATTESTATION_ID.E_PASSPORT).toString(), + deployedActors.mockPassport, + "test-scope", + new Array(88).fill("1"), + "1", + imt, + "20", + undefined, + undefined, + forbiddenCountriesList, + (await deployedActors.user1.getAddress()).slice(2) + ); + snapshotId = await ethers.provider.send("evm_snapshot", []); + }); + + beforeEach(async () => { + vcAndDiscloseProof = structuredClone(baseVcAndDiscloseProof); + }); + + afterEach(async () => { + await ethers.provider.send("evm_revert", [snapshotId]); + snapshotId = await ethers.provider.send("evm_snapshot", []); + }); + + describe("verifyAll", () => { + it("should verify and get result successfully", async () => { + const {registry, owner} = deployedActors; + + const tx = await registry.connect(owner).devAddIdentityCommitment( + ATTESTATION_ID.E_PASSPORT, + nullifier, + commitment + ); + const receipt = await tx.wait() as any; + const timestamp = (await ethers.provider.getBlock(receipt.blockNumber))!.timestamp; + + const vcAndDiscloseHubProof = { + olderThanEnabled: true, + olderThan: "20", + forbiddenCountriesEnabled: true, + forbiddenCountriesListPacked: forbiddenCountriesListPacked, + ofacEnabled: true, + vcAndDiscloseProof: vcAndDiscloseProof + }; + + const types = ['0', '1', '2']; // Example types + const [readableData, success] = await verifyAll.verifyAll( + timestamp, + vcAndDiscloseHubProof, + types + ); + + expect(success).to.be.true; + expect(readableData.name).to.not.be.empty; + + }); + + it("should verify and get result successfully with out timestamp verification", async () => { + const {registry, owner} = deployedActors; + + await registry.connect(owner).devAddIdentityCommitment( + ATTESTATION_ID.E_PASSPORT, + nullifier, + commitment + ); + const vcAndDiscloseHubProof = { + olderThanEnabled: true, + olderThan: "20", + forbiddenCountriesEnabled: true, + forbiddenCountriesListPacked: forbiddenCountriesListPacked, + ofacEnabled: true, + vcAndDiscloseProof: vcAndDiscloseProof + }; + + const types = ['0', '1', '2']; // Example types + const [readableData, success] = await verifyAll.verifyAll( + 0, + vcAndDiscloseHubProof, + types + ); + + expect(success).to.be.true; + expect(readableData.name).to.not.be.empty; + + }); + + it("should return empty result when verification fails", async () => { + const {registry, owner} = deployedActors; + + await registry.connect(owner).devAddIdentityCommitment( + ATTESTATION_ID.E_PASSPORT, + nullifier, + commitment + ); + + vcAndDiscloseProof.pubSignals[CIRCUIT_CONSTANTS.VC_AND_DISCLOSE_MERKLE_ROOT_INDEX] = generateRandomFieldElement(); + const vcAndDiscloseHubProof = { + olderThanEnabled: true, + olderThan: "20", + forbiddenCountriesEnabled: true, + forbiddenCountriesListPacked: forbiddenCountriesListPacked, + ofacEnabled: true, + vcAndDiscloseProof: vcAndDiscloseProof + }; + + const types = ['0', '1', '2']; + const [readableData, success] = await verifyAll.verifyAll( + 0, + vcAndDiscloseHubProof, + types + ); + + expect(success).to.be.false; + expect(readableData.name).to.be.empty; + + }); + + it("should fail with invalid root timestamp", async () => { + const {registry, owner} = deployedActors; + + await registry.connect(owner).devAddIdentityCommitment( + ATTESTATION_ID.E_PASSPORT, + nullifier, + commitment + ); + + const vcAndDiscloseHubProof = { + olderThanEnabled: true, + olderThan: "20", + forbiddenCountriesEnabled: true, + forbiddenCountriesListPacked: forbiddenCountriesListPacked, + ofacEnabled: true, + vcAndDiscloseProof: vcAndDiscloseProof + }; + + const types = ['0', '1', '2']; + const [readableData, success] = await verifyAll.verifyAll( + 123456, // Invalid timestamp + vcAndDiscloseHubProof, + types + ); + + expect(success).to.be.false; + expect(readableData.name).to.be.empty; + }); + }); + + describe("admin functions", () => { + it("should allow owner to set new hub address", async () => { + const newHubAddress = await deployedActors.user1.getAddress(); + await verifyAll.setHub(newHubAddress); + }); + + it("should allow owner to set new registry address", async () => { + const newRegistryAddress = await deployedActors.user1.getAddress(); + await verifyAll.setRegistry(newRegistryAddress); + }); + + it("should not allow non-owner to set new hub address", async () => { + const newHubAddress = await deployedActors.user1.getAddress(); + await expect( + verifyAll.connect(deployedActors.user1).setHub(newHubAddress) + ).to.be.revertedWithCustomError(verifyAll, "OwnableUnauthorizedAccount"); + }); + + it("should not allow non-owner to set new registry address", async () => { + const newRegistryAddress = await deployedActors.user1.getAddress(); + await expect( + verifyAll.connect(deployedActors.user1).setRegistry(newRegistryAddress) + ).to.be.revertedWithCustomError(verifyAll, "OwnableUnauthorizedAccount"); + }); + }); +}); diff --git a/contracts/test/unit/formatter.test.ts b/contracts/test/unit/formatter.test.ts index 30cec9c71..d46c2c720 100644 --- a/contracts/test/unit/formatter.test.ts +++ b/contracts/test/unit/formatter.test.ts @@ -50,6 +50,54 @@ describe("Formatter", function () { expect(() => Formatter.formatDate(input)) .to.throw("InvalidDateLength"); }); + + it("should handle errors consistently when month is out of range", async function () { + const input = "941331"; + await expect(testFormatter.testFormatDate(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidMonthRange"); + expect(() => Formatter.formatDate(input)) + .to.throw("InvalidMonthRange"); + }); + + it("should handle errors consistently when month is out of range (more than 20)", async function () { + const input = "942032"; + await expect(testFormatter.testFormatDate(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidMonthRange"); + expect(() => Formatter.formatDate(input)) + .to.throw("InvalidMonthRange"); + }); + + it("should handle errors consistently when day is out of range (more than 31)", async function () { + const input = "940132"; + await expect(testFormatter.testFormatDate(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidDayRange"); + expect(() => Formatter.formatDate(input)) + .to.throw("InvalidDayRange"); + }); + + it("should handle errors consistently when day is out of range (more than 40)", async function () { + const input = "940140"; + await expect(testFormatter.testFormatDate(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidDayRange"); + expect(() => Formatter.formatDate(input)) + .to.throw("InvalidDayRange"); + }); + + it("should handle errors consistently when input is not a number", async function () { + const input = "94012a"; + await expect(testFormatter.testFormatDate(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidAsciiCode"); + expect(() => Formatter.formatDate(input)) + .to.throw("InvalidAsciiCode"); + }); + + it("should handle errors consistently when input is not a number", async function () { + const input = "94012."; + await expect(testFormatter.testFormatDate(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidAsciiCode"); + expect(() => Formatter.formatDate(input)) + .to.throw("InvalidAsciiCode"); + }); }); describe("numAsciiToUint", function () { @@ -88,6 +136,36 @@ describe("Formatter", function () { const tsResult =toHexString(Formatter.fieldElementsToBytes(input as [bigint, bigint, bigint])); expect(contractResult).to.deep.equal(tsResult); }); + + it("should revert when field element is out of range", async function () { + const input = [ + 21888242871839275222246405745257275088548364400416034343698204186575808495617n, + 0n, + 0n + ] as [bigint, bigint, bigint]; + await expect(testFormatter.testFieldElementsToBytes(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidFieldElement"); + }); + + it("should revert when field element is out of range", async function () { + const input = [ + 0n, + 21888242871839275222246405745257275088548364400416034343698204186575808495617n, + 0n + ] as [bigint, bigint, bigint]; + await expect(testFormatter.testFieldElementsToBytes(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidFieldElement"); + }); + + it("should revert when field element is out of range", async function () { + const input = [ + 0n, + 0n, + 21888242871839275222246405745257275088548364400416034343698204186575808495617n + ] as [bigint, bigint, bigint]; + await expect(testFormatter.testFieldElementsToBytes(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidFieldElement"); + }); }); describe("extractForbiddenCountriesFromPacked", function () { @@ -100,6 +178,12 @@ describe("Formatter", function () { expect(contractResult[1]).to.equal("BBB"); expect(contractResult[2]).to.equal("AAA"); }); + + it("should revert when field element is out of range", async function () { + const input = 21888242871839275222246405745257275088548364400416034343698204186575808495617n; + await expect(testFormatter.testExtractForbiddenCountriesFromPacked(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidFieldElement"); + }); }); describe("proofDateToUnixTimestamp", function () { @@ -126,6 +210,12 @@ describe("Formatter", function () { expect(contractResult).to.equal(testCase.expected); } }); + + it("should revert when date digit is out of range", async function () { + const input = [9, 4, 0, 1, 2, 10]; + await expect(testFormatter.testProofDateToUnixTimestamp(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidDateDigit"); + }); }); describe("dateToUnixTimestamp", function () { @@ -156,6 +246,43 @@ describe("Formatter", function () { expect(() => Formatter.dateToUnixTimestamp(input)) .to.throw("InvalidDateLength"); }); + + it("should revert when month is out of range (more than 12)", async function () { + const input = "941331"; + await expect(testFormatter.testDateToUnixTimestamp(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidMonthRange"); + }); + + it("should revert when month is out of range (more than 20)", async function () { + const input = "942031"; + await expect(testFormatter.testDateToUnixTimestamp(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidMonthRange"); + }); + + it("should revert when day is out of range (more than 31)", async function () { + const input = "940132"; + await expect(testFormatter.testDateToUnixTimestamp(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidDayRange"); + }); + + it("should revert when day is out of range (more than 40)", async function () { + const input = "940140"; + await expect(testFormatter.testDateToUnixTimestamp(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidDayRange"); + }); + + it("should revert when date digit is out of range", async function () { + const input = "94012a"; + await expect(testFormatter.testDateToUnixTimestamp(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidAsciiCode"); + }); + + it("should revert when date digit is out of range", async function () { + const input = "94012."; + await expect(testFormatter.testDateToUnixTimestamp(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidAsciiCode"); + }); + }); describe("substring", function () { @@ -199,6 +326,19 @@ describe("Formatter", function () { expect(contractResult).to.equal(testCase.expected); } }); + + it("should revert when input is not a number", async function () { + const input = "12a"; + await expect(testFormatter.testParseDatePart(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidAsciiCode"); + }); + + + it("should revert when input is not a number", async function () { + const input = "12."; + await expect(testFormatter.testParseDatePart(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidAsciiCode"); + }); }); describe("toTimestamp", function () { @@ -233,6 +373,43 @@ describe("Formatter", function () { expect(contractResult).to.equal(testCase.expected); } }); + + it("should revert when year is out of range", async function () { + const input = 1969; + await expect(testFormatter.testToTimestamp(input, 1, 1)) + .to.be.revertedWithCustomError(testFormatter, "InvalidYearRange"); + }); + + it("should revert when year is out of range", async function () { + const input = 2101; + await expect(testFormatter.testToTimestamp(input, 1, 1)) + .to.be.revertedWithCustomError(testFormatter, "InvalidYearRange"); + }); + + it("should revert when month is out of range", async function () { + const input = 13; + await expect(testFormatter.testToTimestamp(2000, input, 1)) + .to.be.revertedWithCustomError(testFormatter, "InvalidMonthRange"); + }); + + it("should revert when month is out of range", async function () { + const input = 0; + await expect(testFormatter.testToTimestamp(2000, input, 1)) + .to.be.revertedWithCustomError(testFormatter, "InvalidMonthRange"); + }); + + it("should revert when day is out of range", async function () { + const input = 32; + await expect(testFormatter.testToTimestamp(2000, 1, input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidDayRange"); + }); + + it("should revert when day is out of range", async function () { + const input = 0; + await expect(testFormatter.testToTimestamp(2000, 1, input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidDayRange"); + }); + }); describe("isLeapYear", function () { @@ -240,8 +417,10 @@ describe("Formatter", function () { const testCases = [ { year: 2000, expected: true }, { year: 2020, expected: true }, + { year: 2001, expected: false }, + { year: 2042, expected: false }, { year: 2100, expected: false }, - { year: 2023, expected: false } + { year: 1970, expected: false }, ]; for (const testCase of testCases) { @@ -251,6 +430,20 @@ describe("Formatter", function () { expect(contractResult).to.equal(testCase.expected); } }); + + it("should revert when year is out of range", async function () { + const input = 1969; + await expect(testFormatter.testIsLeapYear(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidYearRange"); + }); + + it("should revert when year is out of range", async function () { + const input = 2101; + await expect(testFormatter.testIsLeapYear(input)) + .to.be.revertedWithCustomError(testFormatter, "InvalidYearRange"); + }); + + }); }); diff --git a/contracts/test/utils/formatter.ts b/contracts/test/utils/formatter.ts index 65949a3d1..abea561ea 100644 --- a/contracts/test/utils/formatter.ts +++ b/contracts/test/utils/formatter.ts @@ -32,9 +32,27 @@ export class Formatter { if (date.length !== 6) { throw new Error("InvalidDateLength"); } - const year = Formatter.substring(date, 0, 2); - const month = Formatter.substring(date, 2, 4); - const day = Formatter.substring(date, 4, 6); + + const dateBytes = Array.from(date); + + for (let i = 0; i < 6; i++) { + if (dateBytes[i] < '0' || dateBytes[i] > '9') { + throw new Error("InvalidAsciiCode"); + } + } + + if (dateBytes[2] > '1' || (dateBytes[2] === '1' && dateBytes[3] > '2')) { + throw new Error("InvalidMonthRange"); + } + + if (dateBytes[4] > '3' || (dateBytes[4] === '3' && dateBytes[5] > '1')) { + throw new Error("InvalidDayRange"); + } + + const year = date.substring(0, 2); + const month = date.substring(2, 4); + const day = date.substring(4, 6); + return `${day}-${month}-${year}`; } diff --git a/sdk/core/src/AttestationVerifier.ts b/sdk/core/src/AttestationVerifier.ts index 1d5a02c91..7a0bc4b4a 100644 --- a/sdk/core/src/AttestationVerifier.ts +++ b/sdk/core/src/AttestationVerifier.ts @@ -76,7 +76,6 @@ export class AttestationVerifier { vcAndDiscloseProof: solidityProof } - const result = await this.verifyAllContract.verifyAll( this.targetRootTimestamp, vcAndDiscloseHubProof,