diff --git a/.editorconfig b/.editorconfig index af0b7e2..e6e8824 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,7 +7,7 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space -indent_size = 2 +indent_size = 4 [*.{yml,yaml}] indent_size = 2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 90a74ec..5e02e5e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,12 +27,9 @@ jobs: run: make build - name: Run Tests - run: make test + run: | + make test + make snapshot - name: Check formatting run: make format-check - - - name: Run Benchmarks - run: | - make bench - make snapshot diff --git a/foundry.toml b/foundry.toml index 019b255..d598c09 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,8 +1,8 @@ # === Default Profile ========================================================= [profile.default] -solc = "0.8.21" -evm_version = "paris" +solc = "0.8.24" +evm_version = "cancun" src = 'src' out = 'out' libs = ['lib'] diff --git a/lib/forge-std b/lib/forge-std index 06c5a8f..4d63c97 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 06c5a8f760f7d2392697cb092eda80c864e4fc06 +Subproject commit 4d63c978718517fa02d4e330fbe7372dbb06c2f1 diff --git a/src/WorldIDIdentityManagerImplV3.sol b/src/WorldIDIdentityManagerImplV3.sol new file mode 100644 index 0000000..0b3b644 --- /dev/null +++ b/src/WorldIDIdentityManagerImplV3.sol @@ -0,0 +1,303 @@ +pragma solidity ^0.8.24; + +import "./WorldIDIdentityManagerImplV2.sol"; +import "./interfaces/ITreeVerifier4844.sol"; +import {VerifierLookupTable4844} from "./data/VerifierLookupTable4844.sol"; + +/// @title WorldID Identity Manager Implementation Version 3 +/// @author Worldcoin +/// @notice An implementation of a batch-based identity manager for the WorldID protocol. +/// @dev The manager is based on the principle of verifying externally-created Zero Knowledge Proofs +/// to perform the insertion using EIP-4844. +/// @dev This is the implementation delegated to by a proxy. +contract WorldIDIdentityManagerImplV3 is WorldIDIdentityManagerImplV2 { + /////////////////////////////////////////////////////////////////////////////// + /// A NOTE ON IMPLEMENTATION CONTRACTS /// + /////////////////////////////////////////////////////////////////////////////// + + // This contract is designed explicitly to operate from behind a proxy contract. As a result, + // there are a few important implementation considerations: + // + // - All updates made after deploying a given version of the implementation should inherit from + // the latest version of the implementation. This contract inherits from its previous implementation + // WorldIDIdentityManagerImplV2. This prevents storage clashes. + // - All functions that are less access-restricted than `private` should be marked `virtual` in + // order to enable the fixing of bugs in the existing interface. + // - Any function that reads from or modifies state (i.e. is not marked `pure`) must be + // annotated with the `onlyProxy` and `onlyInitialized` modifiers. This ensures that it can + // only be called when it has access to the data in the proxy, otherwise results are likely to + // be nonsensical. + // - This contract deals with important data for the WorldID system. Ensure that all newly-added + // functionality is carefully access controlled using `onlyOwner`, or a more granular access + // mechanism. + // - Do not assign any contract-level variables at the definition site unless they are + // `constant`. + // + // Additionally, the following notes apply: + // + // - Initialisation and ownership management are not protected behind `onlyProxy` intentionally. + // This ensures that the contract can safely be disposed of after it is no longer used. + // - Carefully consider what data recovery options are presented as new functionality is added. + // Care must be taken to ensure that a migration plan can exist for cases where upgrades + // cannot recover from an issue or vulnerability. + + /////////////////////////////////////////////////////////////////////////////// + /// !!!!! DATA: DO NOT REORDER !!!!! /// + /////////////////////////////////////////////////////////////////////////////// + + // To ensure compatibility between upgrades, it is exceedingly important that no reordering of + // these variables takes place. If reordering happens, a storage clash will occur (effectively a + // memory safety error). + + /// @notice The table of verifiers for verifying batch identity insertions following the EIP-4844 scheme. + VerifierLookupTable4844 internal batchInsertion4844Verifiers; + + /// @notice Thrown when the WorldIDIdentityManagerImplV3 contract is initialized + event WorldIDIdentityManagerImplV3Initialized(); + + /// @notice Initializes the V3 implementation contract. + /// @param _batchInsertion4844Verifiers The table of verifiers for verifying batch identity insertions. + /// @dev Must be called exactly once + /// @dev This is marked `reinitializer()` to allow for updated initialisation steps when working + /// with upgrades based upon this contract. Be aware that there are only 256 (zero-indexed) + /// initialisations allowed, so decide carefully when to use them. Many cases can safely be + /// replaced by use of setters. + /// @dev This function is explicitly not virtual as it does not make sense to override even when + /// upgrading. Create a separate initializer function instead. + /// + /// + /// @custom:reverts InvalidVerifierLUT if `_batchInsertion4844Verifiers` is set to the zero address + function initializeV3(VerifierLookupTable4844 _batchInsertion4844Verifiers) + public + reinitializer(3) + { + if (address(_batchInsertion4844Verifiers) == address(0)) { + revert InvalidVerifierLUT(); + } + + batchInsertion4844Verifiers = _batchInsertion4844Verifiers; + + emit WorldIDIdentityManagerImplV3Initialized(); + } + + /////////////////////////////////////////////////////////////////////////////// + /// UTILITY FUNCTIONS /// + /////////////////////////////////////////////////////////////////////////////// + + /// @notice Gets the address for the lookup table of merkle tree verifiers used for identity + /// registrations. + /// + /// @return addr The address of the contract being used as the verifier lookup table. + function getRegisterIdentities4844VerifierLookupTableAddress() + public + view + virtual + onlyProxy + onlyInitialized + returns (address) + { + return address(batchInsertion4844Verifiers); + } + + /// @notice Sets the address for the lookup table of merkle tree verifiers used for identity + /// registrations. + /// @dev Only the owner of the contract can call this function. + /// + /// @param newTable The new verifier lookup table to be used for verifying identity + /// registrations. + /// @custom:reverts InvalidVerifierLUT if `newTable` is set to the zero address + function setRegisterIdentities4844VerifierLookupTable(VerifierLookupTable4844 newTable) + public + virtual + onlyProxy + onlyInitialized + onlyOwner + { + if (address(newTable) == address(0)) { + revert InvalidVerifierLUT(); + } + + VerifierLookupTable4844 oldTable = batchInsertion4844Verifiers; + batchInsertion4844Verifiers = newTable; + emit DependencyUpdated( + Dependency.InsertionVerifierLookupTable, address(oldTable), address(newTable) + ); + } + + /////////////////////////////////////////////////////////////////////////////// + /// PUBLIC TYPES /// + /////////////////////////////////////////////////////////////////////////////// + + /// @notice Parameters for registerIdentities4844 function. + /// @dev This struct holds the parameters for registering identities and verifying the insertion proof + /// using KZG proofs as described in EIP-4844. + struct RegisterIdentities4844Params { + /// @notice The proof that given the conditions, insertion into the tree results in `postRoot`. + /// Elements 0 and 1 are the `x` and `y` coordinates for `ar` respectively. Elements 2 and 3 are the + /// `x` coordinate for `bs`, and elements 4 and 5 are the `y` coordinate for `bs`. Elements 6 and 7 + /// are the `x` and `y` coordinates for `krs`. + uint256[8] insertionProof; + /// @notice The Pedersen commitments from the proof. + uint256[2] commitments; + /// @notice The proof of knowledge for the Pedersen commitments. + uint256[2] commitmentPok; + /// @notice KZG commitment for the polynomial extrapolated from the identities. + uint128[3] kzgCommitment; + /// @notice KZG proof associated with the commitment. + uint128[3] kzgProof; + /// @notice Expected evaluation of the polynomial at a certain point equal to KZG challenge. + uint256 expectedEvaluation; + /// @notice The value for the root of the tree before the `identityCommitments` have been inserted. + /// Must be an element of the field `Kr`. (already in reduced form) + uint256 preRoot; + /// @notice The root obtained after inserting all of `identityCommitments` into the tree described + /// by `preRoot`. Must be an element of the field `Kr`. (already in reduced form) + uint256 postRoot; + /// @notice Hash of all inserted identities, constructed by taking a root of the Merkle Tree of minimal + /// depth containing all identities. + bytes32 inputHash; + /// @notice Number of identities being registered in this batch. + uint32 batchSize; + /// @notice The position in the tree at which the insertions were made. + uint32 startIndex; + } + + /////////////////////////////////////////////////////////////////////////////// + /// ERRORS /// + /////////////////////////////////////////////////////////////////////////////// + + /// @notice Thrown when the point evaluation precompile returns failure. + /// This means KZG proof cannot be verified. + error KzgProofVerificationFailed(); + + /////////////////////////////////////////////////////////////////// + /// IDENTITY MANAGEMENT /// + /////////////////////////////////////////////////////////////////// + + /// @notice Registers identities into the WorldID system following the EIP-4844 scheme. + /// @dev Can only be called by the identity operator. + /// @dev Registration is performed off-chain and verified on-chain via insertion proof + /// and KZG proof. This saves gas and time over inserting identities one at a time. + /// + /// @param params parameters for the process defined by the `RegisterIdentities4844Params` structure. + /// @custom:reverts Unauthorized If the message sender is not authorised to add identities. + /// @custom:reverts NotLatestRoot If the provided `params.preRoot` is not the latest root. + /// @custom:reverts ProofValidationFailure If `params.insertionProof` cannot be verified using the + /// provided inputs. + /// @custom:reverts VerifierLookupTable.NoSuchVerifier If the batch sizes doesn't match a known + /// verifier. + /// @custom:reverts KzgProofVerificationFailed If KZG proof verification fails + function registerIdentities4844(RegisterIdentities4844Params calldata params) + public + virtual + onlyProxy + onlyInitialized + onlyIdentityOperator + { + if (params.preRoot != _latestRoot) { + revert NotLatestRoot(params.preRoot, _latestRoot); + } + + bytes32 kzgCommitmentHash = blobhash(0); + bytes32 kzgChallenge = computeKzgChallenge(params.inputHash, kzgCommitmentHash); + bool success = evaluatePoint( + kzgCommitmentHash, + kzgChallenge, + bytes32(params.expectedEvaluation), + params.kzgCommitment, + params.kzgProof + ); + if (!success) { + revert KzgProofVerificationFailed(); + } + + // We need to look up the correct verifier before we can verify. + ITreeVerifier4844 insertionVerifier = + batchInsertion4844Verifiers.getVerifierFor(params.batchSize); + + // With that, we can properly try and verify. + try insertionVerifier.verifyProof( + params.insertionProof, + params.commitments, + params.commitmentPok, + [ + uint256(params.inputHash), + params.expectedEvaluation % SNARK_SCALAR_FIELD, + uint256(kzgCommitmentHash), + uint256(params.startIndex), + params.preRoot, + params.postRoot + ] + ) { + // If it did verify, we need to update the contract's state. We set the currently valid + // root to the root after the insertions. + _latestRoot = params.postRoot; + + // We also need to add the previous root to the history, and set the timestamp at + // which it was expired. + rootHistory[params.preRoot] = uint128(block.timestamp); + + emit TreeChanged(params.preRoot, TreeChange.Insertion, params.postRoot); + } catch Error(string memory errString) { + /// This is not the revert we're looking for. + revert(errString); + } catch { + // If we reach here we know it's the internal error, as the tree verifier only uses + // `require`s otherwise, which will be re-thrown above. + revert ProofValidationFailure(); + } + } + + /////////////////////////////////////////////////////////////////////////////// + /// UTILITY FUNCTIONS /// + /////////////////////////////////////////////////////////////////////////////// + + address constant PRECOMPILE_POINT_EVALUATION = address(0x0a); + + /// @notice Call the point evaluation precompiled contract. + /// Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof. + /// Also verify that the provided commitment matches the provided versioned_hash. + /// @param versioned_hash Reference to a blob in the execution layer (obtained from the data storage or execution environment). + /// @param x x-coordinate at which the blob is being evaluated. + /// @param y y-coordinate at which the blob is being evaluated. + /// @param commitment Commitment to the blob being evaluated (obtained from the KZG commitment scheme). + /// @param kzgProof Proof associated with the commitment (obtained from the KZG proof generation). + /// @return True on success, false otherwise. + function evaluatePoint( + bytes32 versioned_hash, + bytes32 x, + bytes32 y, + uint128[3] calldata commitment, + uint128[3] calldata kzgProof + ) public view returns (bool) { + bytes memory input = abi.encodePacked( + versioned_hash, + x, + y, + commitment[0], + commitment[1], + commitment[2], + kzgProof[0], + kzgProof[1], + kzgProof[2] + ); + (bool success,) = PRECOMPILE_POINT_EVALUATION.staticcall(input); + return success; + } + + /// @notice Converts input values to a KZG challenge. + /// @dev The challenge is defined as a bytes32 value of a keccak256 hash of the concatenated inputs reduced by BN254 modulus. + /// @param inputHash Hash of the input data calculated as described in the comment + /// to `calculateIdentityRegistrationInputHash()`. + /// @param kzgCommitmentVersionedHash versioned hash of the KZG commitment. + /// @return challenge The reduced keccak256 hash. + function computeKzgChallenge(bytes32 inputHash, bytes32 kzgCommitmentVersionedHash) + public + pure + returns (bytes32) + { + bytes memory inputBytes = abi.encodePacked(inputHash, kzgCommitmentVersionedHash); + uint256 reducedHash = uint256(keccak256(inputBytes)) % SNARK_SCALAR_FIELD; + return bytes32(reducedHash); + } +} diff --git a/src/data/VerifierLookupTable4844.sol b/src/data/VerifierLookupTable4844.sol new file mode 100644 index 0000000..61d6e5b --- /dev/null +++ b/src/data/VerifierLookupTable4844.sol @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Ownable2Step} from "openzeppelin-contracts/access/Ownable2Step.sol"; + +import {ITreeVerifier4844} from "../interfaces/ITreeVerifier4844.sol"; + +/// @title Batch Lookup Table for EIP-4844 proofs. +/// @author Worldcoin +/// @notice A table that provides the correct tree verifier based on the provided batch size. +/// @dev It should be used to query the correct verifier before using that verifier for verifying a +/// tree modification proof. +contract VerifierLookupTable4844 is Ownable2Step { + //////////////////////////////////////////////////////////////////////////////// + /// DATA /// + //////////////////////////////////////////////////////////////////////////////// + + /// The null address. + address internal constant nullAddress = address(0x0); + + /// The null verifiers. + ITreeVerifier4844 internal constant nullVerifier = ITreeVerifier4844(nullAddress); + + /// The lookup table for routing batches. + /// + /// As we expect to only have a few batch sizes per contract, a mapping is used due to its + /// natively sparse storage. + mapping(uint256 => ITreeVerifier4844) internal verifier_lut; + + //////////////////////////////////////////////////////////////////////////////// + /// ERRORS /// + //////////////////////////////////////////////////////////////////////////////// + + /// @notice Raised if a batch size is requested that the lookup table doesn't know about. + error NoSuchVerifier(); + + /// @notice Raised if an attempt is made to add a verifier for a batch size that already exists. + error VerifierExists(); + + /// @notice Thrown when an attempt is made to renounce ownership. + error CannotRenounceOwnership(); + + //////////////////////////////////////////////////////////////////////////////// + /// EVENTS /// + //////////////////////////////////////////////////////////////////////////////// + + /// @notice Emitted when a verifier is added to the lookup table. + /// + /// @param batchSize The size of the batch that the verifier has been added for. + /// @param verifierAddress The address of the verifier that was associated with `batchSize`. + event VerifierAdded(uint256 indexed batchSize, address indexed verifierAddress); + + /// @notice Emitted when a verifier is updated in the lookup table. + /// + /// @param batchSize The size of the batch that the verifier has been updated for. + /// @param oldVerifierAddress The address of the old verifier for `batchSize`. + /// @param newVerifierAddress The address of the new verifier for `batchSize`. + event VerifierUpdated( + uint256 indexed batchSize, + address indexed oldVerifierAddress, + address indexed newVerifierAddress + ); + + /// @notice Emitted when a verifier is disabled in the lookup table. + /// + /// @param batchSize The batch size that had its verifier disabled. + event VerifierDisabled(uint256 indexed batchSize); + + //////////////////////////////////////////////////////////////////////////////// + /// CONSTRUCTION /// + //////////////////////////////////////////////////////////////////////////////// + + /// @notice Constructs a new batch lookup table. + /// @dev It is initially constructed without any verifiers. + constructor() Ownable2Step() {} + + //////////////////////////////////////////////////////////////////////////////// + /// ACCESSORS /// + //////////////////////////////////////////////////////////////////////////////// + + /// @notice Obtains the verifier for the provided `batchSize`. + /// + /// @param batchSize The batch size to get the associated verifier for. + /// + /// @return verifier The tree verifier for the provided `batchSize`. + /// + /// @custom:reverts NoSuchVerifier If there is no verifier associated with the `batchSize`. + function getVerifierFor(uint256 batchSize) public view returns (ITreeVerifier4844 verifier) { + // Check the preconditions for querying the verifier. + validateVerifier(batchSize); + + // With the preconditions checked, we can return the verifier. + verifier = verifier_lut[batchSize]; + } + + /// @notice Adds a verifier for the provided `batchSize`. + /// + /// @param batchSize The batch size to add the verifier for. + /// @param verifier The verifier for a batch of size `batchSize`. + /// + /// @custom:reverts VerifierExists If `batchSize` already has an associated verifier. + /// @custom:reverts string If the caller is not the owner. + function addVerifier(uint256 batchSize, ITreeVerifier4844 verifier) public onlyOwner { + // Check that there is no entry for that batch size. + if (verifier_lut[batchSize] != nullVerifier) { + revert VerifierExists(); + } + + // Add the verifier. + updateVerifier(batchSize, verifier); + emit VerifierAdded(batchSize, address(verifier)); + } + + /// @notice Updates the verifier for the provided `batchSize`. + /// + /// @param batchSize The batch size to add the verifier for. + /// @param verifier The verifier for a batch of size `batchSize`. + /// + /// @return oldVerifier The old verifier instance associated with this batch size. + /// + /// @custom:reverts string If the caller is not the owner. + function updateVerifier(uint256 batchSize, ITreeVerifier4844 verifier) + public + onlyOwner + returns (ITreeVerifier4844 oldVerifier) + { + oldVerifier = verifier_lut[batchSize]; + verifier_lut[batchSize] = verifier; + emit VerifierUpdated(batchSize, address(oldVerifier), address(verifier)); + } + + /// @notice Disables the verifier for the provided batch size. + /// + /// @param batchSize The batch size to disable the verifier for. + /// + /// @return oldVerifier The old verifier associated with the batch size. + /// + /// @custom:reverts string If the caller is not the owner. + function disableVerifier(uint256 batchSize) + public + onlyOwner + returns (ITreeVerifier4844 oldVerifier) + { + oldVerifier = updateVerifier(batchSize, ITreeVerifier4844(nullAddress)); + emit VerifierDisabled(batchSize); + } + + //////////////////////////////////////////////////////////////////////////////// + /// INTERNAL FUNCTIONALITY /// + //////////////////////////////////////////////////////////////////////////////// + + /// @notice Checks if the entry for the provided `batchSize` is a valid verifier. + /// + /// @param batchSize The batch size to check. + /// + /// @custom:reverts NoSuchVerifier If `batchSize` does not have an associated verifier. + /// @custom:reverts BatchTooLarge If `batchSize` exceeds the maximum batch size. + function validateVerifier(uint256 batchSize) internal view { + if (verifier_lut[batchSize] == nullVerifier) { + revert NoSuchVerifier(); + } + } + + //////////////////////////////////////////////////////////////////////////////// + /// OWNERSHIP MANAGEMENT /// + //////////////////////////////////////////////////////////////////////////////// + + /// @notice Ensures that ownership of the lookup table cannot be renounced. + /// @dev This function is intentionally not `virtual` as we do not want it to be possible to + /// renounce ownership for the lookup table. + /// @dev This function is marked as `onlyOwner` to maintain the access restriction from the base + /// contract. + function renounceOwnership() public view override onlyOwner { + revert CannotRenounceOwnership(); + } +} diff --git a/src/interfaces/ITreeVerifier4844.sol b/src/interfaces/ITreeVerifier4844.sol new file mode 100644 index 0000000..6b6a75c --- /dev/null +++ b/src/interfaces/ITreeVerifier4844.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/// @title Tree Verifier Interface +/// @author Worldcoin +/// @notice An interface representing a merkle tree verifier for EIP-4844 proofs. +interface ITreeVerifier4844 { + /// Verify an uncompressed Groth16 proof. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was successfully verified. + /// @param proof the points (A, B, C) in EIP-197 format matching the output + /// of compressProof. + /// @param commitments the Pedersen commitments from the proof. + /// @param commitmentPok the proof of knowledge for the Pedersen commitments. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyProof( + uint256[8] calldata proof, + uint256[2] calldata commitments, + uint256[2] calldata commitmentPok, + uint256[6] calldata input + ) external; +} diff --git a/src/test/InsertionTreeVerifier164844.sol b/src/test/InsertionTreeVerifier164844.sol new file mode 100644 index 0000000..c7bcf22 --- /dev/null +++ b/src/test/InsertionTreeVerifier164844.sol @@ -0,0 +1,757 @@ +import {ITreeVerifier4844} from "src/interfaces/ITreeVerifier4844.sol"; + +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/// @title Groth16 verifier template. +/// @author Remco Bloemen +/// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed +/// (256 bytes) and compressed (128 bytes) format. A view function is provided +/// to compress proofs. +/// @notice See for further explanation. +contract Verifier is ITreeVerifier4844 { + /// Some of the provided public input values are larger than the field modulus. + /// @dev Public input elements are not automatically reduced, as this is can be + /// a dangerous source of bugs. + error PublicInputNotInField(); + + /// The proof is invalid. + /// @dev This can mean that provided Groth16 proof points are not on their + /// curves, that pairing equation fails, or that the proof is not for the + /// provided public input. + error ProofInvalid(); + /// The commitment is invalid + /// @dev This can mean that provided commitment points and/or proof of knowledge are not on their + /// curves, that pairing equation fails, or that the commitment and/or proof of knowledge is not for the + /// commitment key. + error CommitmentInvalid(); + + // Addresses of precompiles + uint256 constant PRECOMPILE_MODEXP = 0x05; + uint256 constant PRECOMPILE_ADD = 0x06; + uint256 constant PRECOMPILE_MUL = 0x07; + uint256 constant PRECOMPILE_VERIFY = 0x08; + + // Base field Fp order P and scalar field Fr order R. + // For BN254 these are computed as follows: + // t = 4965661367192848881 + // P = 36⋅t⁴ + 36⋅t³ + 24⋅t² + 6⋅t + 1 + // R = 36⋅t⁴ + 36⋅t³ + 18⋅t² + 6⋅t + 1 + uint256 constant P = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47; + uint256 constant R = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001; + + // Extension field Fp2 = Fp[i] / (i² + 1) + // Note: This is the complex extension field of Fp with i² = -1. + // Values in Fp2 are represented as a pair of Fp elements (a₀, a₁) as a₀ + a₁⋅i. + // Note: The order of Fp2 elements is *opposite* that of the pairing contract, which + // expects Fp2 elements in order (a₁, a₀). This is also the order in which + // Fp2 elements are encoded in the public interface as this became convention. + + // Constants in Fp + uint256 constant FRACTION_1_2_FP = + 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4; + uint256 constant FRACTION_27_82_FP = + 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5; + uint256 constant FRACTION_3_82_FP = + 0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775; + + // Exponents for inversions and square roots mod P + uint256 constant EXP_INVERSE_FP = + 0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2 + uint256 constant EXP_SQRT_FP = 0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4; + + // Groth16 alpha point in G1 + uint256 constant ALPHA_X = + 2708969884581619714583557951222918593768772820936646583136789853819173846869; + uint256 constant ALPHA_Y = + 8043584586634431593445517970972074344995663485134139288455040086659571635007; + + // Groth16 beta point in G2 in powers of i + uint256 constant BETA_NEG_X_0 = + 2959634726718323159960582986494250462813569884099052977631775603535594317697; + uint256 constant BETA_NEG_X_1 = + 2567045386415897874314697628037970624972832810910119870003888717933699604901; + uint256 constant BETA_NEG_Y_0 = + 16901523272247727115591169122896696499546950846485806085170956609925505472585; + uint256 constant BETA_NEG_Y_1 = + 9215676527870136774869096108544312861626222401455601740260979586958004850529; + + // Groth16 gamma point in G2 in powers of i + uint256 constant GAMMA_NEG_X_0 = + 8904268816337492794416634636731345512132126205040334900538636341318421538665; + uint256 constant GAMMA_NEG_X_1 = + 19004930723547195287383134699314663710813331368262617951429852355608517342331; + uint256 constant GAMMA_NEG_Y_0 = + 7842710818119939895862574378653362734227090432895633776678943219170947197152; + uint256 constant GAMMA_NEG_Y_1 = + 2119828549076590392190534591515249836229477358991111181013402454206155157118; + + // Groth16 delta point in G2 in powers of i + uint256 constant DELTA_NEG_X_0 = + 16751268373860897104759671698719630006610347517780893954841247694987729581925; + uint256 constant DELTA_NEG_X_1 = + 17074355565731257728214063587600772000822732044793320082675686505906845346452; + uint256 constant DELTA_NEG_Y_0 = + 19276066093240754710159494686794885924197536752080653828868593863903058741434; + uint256 constant DELTA_NEG_Y_1 = + 10391802489342383226451710090356140861208165419529434978751035344608888508609; + // Pedersen G point in G2 in powers of i + uint256 constant PEDERSEN_G_X_0 = + 191329193964481065495212234619899074886870006108961472779204277869671687115; + uint256 constant PEDERSEN_G_X_1 = + 18448119812880134607636953435856909803133499313412999813309765036275639759714; + uint256 constant PEDERSEN_G_Y_0 = + 7320805055268657491420712525633197949310593554647878873210287658169673900549; + uint256 constant PEDERSEN_G_Y_1 = + 3560501050363463728186450810474818536571923785681141032866912257045688789262; + + // Pedersen GRootSigmaNeg point in G2 in powers of i + uint256 constant PEDERSEN_GROOTSIGMANEG_X_0 = + 211898546590335769030549987216927375237199629913882184437950823448881903970; + uint256 constant PEDERSEN_GROOTSIGMANEG_X_1 = + 11880079357380769452360200537618953332011342321508956754029570189743729673109; + uint256 constant PEDERSEN_GROOTSIGMANEG_Y_0 = + 20343036341560553279453586916303753798495271995175822900148534636526224647164; + uint256 constant PEDERSEN_GROOTSIGMANEG_Y_1 = + 18398984926832230363518321101521122731640509584485783610091427005978221352504; + + // Constant and public input points + uint256 constant CONSTANT_X = + 19358550662348637607112384570170513296928047638941946872811625925166768621689; + uint256 constant CONSTANT_Y = + 6626464468086658346890654816494620493177727644183109536624044536458730824315; + uint256 constant PUB_0_X = + 21039846169407040170900369597064405833466068013105470546221436302050929487572; + uint256 constant PUB_0_Y = + 2611968432498880732075319619253202283148240652063087246949510105674797190280; + uint256 constant PUB_1_X = + 17167433712229735771674828746728526346122363071518232159739058701502221378434; + uint256 constant PUB_1_Y = + 1739891145391694203708089066027069569757327500962996441495332028277069755267; + uint256 constant PUB_2_X = + 17798576048770024481159849810854079136640512359488534233899537955087709779447; + uint256 constant PUB_2_Y = + 3109649566724390234731095093811315543854286970944206387661935696332701907948; + uint256 constant PUB_3_X = + 7890320639901919646211933804150885252994659227692929006872667869290059075281; + uint256 constant PUB_3_Y = + 11396290418620464030035551260992220541169365820463159918373688231853778829859; + uint256 constant PUB_4_X = + 16030714242136825467561218858637637879071023746882506204693189470117499845846; + uint256 constant PUB_4_Y = + 14919105472258793272718206026880812470549694675722205962400380939241926000512; + uint256 constant PUB_5_X = + 15864446105389560446153403630146334838539883248526951175596504258425435272439; + uint256 constant PUB_5_Y = + 3764670878071624467628034771504294179451467482124027272932022992371176433497; + uint256 constant PUB_6_X = + 13240007634426972631439790531670899863212947213748180981433638304827247605238; + uint256 constant PUB_6_Y = + 9107574043090153668746153226362613394226970755908178311480271382776655025033; + + /// Negation in Fp. + /// @notice Returns a number x such that a + x = 0 in Fp. + /// @notice The input does not need to be reduced. + /// @param a the base + /// @return x the result + function negate(uint256 a) internal pure returns (uint256 x) { + unchecked { + x = (P - (a % P)) % P; // Modulo is cheaper than branching + } + } + + /// Exponentiation in Fp. + /// @notice Returns a number x such that a ^ e = x in Fp. + /// @notice The input does not need to be reduced. + /// @param a the base + /// @param e the exponent + /// @return x the result + function exp(uint256 a, uint256 e) internal view returns (uint256 x) { + bool success; + assembly ("memory-safe") { + let f := mload(0x40) + mstore(f, 0x20) + mstore(add(f, 0x20), 0x20) + mstore(add(f, 0x40), 0x20) + mstore(add(f, 0x60), a) + mstore(add(f, 0x80), e) + mstore(add(f, 0xa0), P) + success := staticcall(gas(), PRECOMPILE_MODEXP, f, 0xc0, f, 0x20) + x := mload(f) + } + if (!success) { + // Exponentiation failed. + // Should not happen. + revert ProofInvalid(); + } + } + + /// Invertsion in Fp. + /// @notice Returns a number x such that a * x = 1 in Fp. + /// @notice The input does not need to be reduced. + /// @notice Reverts with ProofInvalid() if the inverse does not exist + /// @param a the input + /// @return x the solution + function invert_Fp(uint256 a) internal view returns (uint256 x) { + x = exp(a, EXP_INVERSE_FP); + if (mulmod(a, x, P) != 1) { + // Inverse does not exist. + // Can only happen during G2 point decompression. + revert ProofInvalid(); + } + } + + /// Square root in Fp. + /// @notice Returns a number x such that x * x = a in Fp. + /// @notice Will revert with InvalidProof() if the input is not a square + /// or not reduced. + /// @param a the square + /// @return x the solution + function sqrt_Fp(uint256 a) internal view returns (uint256 x) { + x = exp(a, EXP_SQRT_FP); + if (mulmod(x, x, P) != a) { + // Square root does not exist or a is not reduced. + // Happens when G1 point is not on curve. + revert ProofInvalid(); + } + } + + /// Square test in Fp. + /// @notice Returns wheter a number x exists such that x * x = a in Fp. + /// @notice Will revert with InvalidProof() if the input is not a square + /// or not reduced. + /// @param a the square + /// @return x the solution + function isSquare_Fp(uint256 a) internal view returns (bool) { + uint256 x = exp(a, EXP_SQRT_FP); + return mulmod(x, x, P) == a; + } + + /// Square root in Fp2. + /// @notice Fp2 is the complex extension Fp[i]/(i^2 + 1). The input is + /// a0 + a1 ⋅ i and the result is x0 + x1 ⋅ i. + /// @notice Will revert with InvalidProof() if + /// * the input is not a square, + /// * the hint is incorrect, or + /// * the input coefficents are not reduced. + /// @param a0 The real part of the input. + /// @param a1 The imaginary part of the input. + /// @param hint A hint which of two possible signs to pick in the equation. + /// @return x0 The real part of the square root. + /// @return x1 The imaginary part of the square root. + function sqrt_Fp2(uint256 a0, uint256 a1, bool hint) + internal + view + returns (uint256 x0, uint256 x1) + { + // If this square root reverts there is no solution in Fp2. + uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P)); + if (hint) { + d = negate(d); + } + // If this square root reverts there is no solution in Fp2. + x0 = sqrt_Fp(mulmod(addmod(a0, d, P), FRACTION_1_2_FP, P)); + x1 = mulmod(a1, invert_Fp(mulmod(x0, 2, P)), P); + + // Check result to make sure we found a root. + // Note: this also fails if a0 or a1 is not reduced. + if ( + a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P) + || a1 != mulmod(2, mulmod(x0, x1, P), P) + ) { + revert ProofInvalid(); + } + } + + /// Compress a G1 point. + /// @notice Reverts with InvalidProof if the coordinates are not reduced + /// or if the point is not on the curve. + /// @notice The point at infinity is encoded as (0,0) and compressed to 0. + /// @param x The X coordinate in Fp. + /// @param y The Y coordinate in Fp. + /// @return c The compresed point (x with one signal bit). + function compress_g1(uint256 x, uint256 y) internal view returns (uint256 c) { + if (x >= P || y >= P) { + // G1 point not in field. + revert ProofInvalid(); + } + if (x == 0 && y == 0) { + // Point at infinity + return 0; + } + + // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid. + uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); + if (y == y_pos) { + return (x << 1) | 0; + } else if (y == negate(y_pos)) { + return (x << 1) | 1; + } else { + // G1 point not on curve. + revert ProofInvalid(); + } + } + + /// Decompress a G1 point. + /// @notice Reverts with InvalidProof if the input does not represent a valid point. + /// @notice The point at infinity is encoded as (0,0) and compressed to 0. + /// @param c The compresed point (x with one signal bit). + /// @return x The X coordinate in Fp. + /// @return y The Y coordinate in Fp. + function decompress_g1(uint256 c) internal view returns (uint256 x, uint256 y) { + // Note that X = 0 is not on the curve since 0³ + 3 = 3 is not a square. + // so we can use it to represent the point at infinity. + if (c == 0) { + // Point at infinity as encoded in EIP196 and EIP197. + return (0, 0); + } + bool negate_point = c & 1 == 1; + x = c >> 1; + if (x >= P) { + // G1 x coordinate not in field. + revert ProofInvalid(); + } + + // Note: (x³ + 3) is irreducible in Fp, so it can not be zero and therefore + // y can not be zero. + // Note: sqrt_Fp reverts if there is no solution, i.e. the point is not on the curve. + y = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P)); + if (negate_point) { + y = negate(y); + } + } + + /// Compress a G2 point. + /// @notice Reverts with InvalidProof if the coefficients are not reduced + /// or if the point is not on the curve. + /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). + /// @param x0 The real part of the X coordinate. + /// @param x1 The imaginary poart of the X coordinate. + /// @param y0 The real part of the Y coordinate. + /// @param y1 The imaginary part of the Y coordinate. + /// @return c0 The first half of the compresed point (x0 with two signal bits). + /// @return c1 The second half of the compressed point (x1 unmodified). + function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1) + internal + view + returns (uint256 c0, uint256 c1) + { + if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) { + // G2 point not in field. + revert ProofInvalid(); + } + if ((x0 | x1 | y0 | y1) == 0) { + // Point at infinity + return (0, 0); + } + + // Compute y^2 + // Note: shadowing variables and scoping to avoid stack-to-deep. + uint256 y0_pos; + uint256 y1_pos; + { + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); + uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); + uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); + y0_pos = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); + y1_pos = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + } + + // Determine hint bit + // If this sqrt fails the x coordinate is not on the curve. + bool hint; + { + uint256 d = sqrt_Fp(addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P)); + hint = !isSquare_Fp(mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P)); + } + + // Recover y + (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint); + if (y0 == y0_pos && y1 == y1_pos) { + c0 = (x0 << 2) | (hint ? 2 : 0) | 0; + c1 = x1; + } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) { + c0 = (x0 << 2) | (hint ? 2 : 0) | 1; + c1 = x1; + } else { + // G1 point not on curve. + revert ProofInvalid(); + } + } + + /// Decompress a G2 point. + /// @notice Reverts with InvalidProof if the input does not represent a valid point. + /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1) + /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i). + /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0). + /// @param c0 The first half of the compresed point (x0 with two signal bits). + /// @param c1 The second half of the compressed point (x1 unmodified). + /// @return x0 The real part of the X coordinate. + /// @return x1 The imaginary poart of the X coordinate. + /// @return y0 The real part of the Y coordinate. + /// @return y1 The imaginary part of the Y coordinate. + function decompress_g2(uint256 c0, uint256 c1) + internal + view + returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1) + { + // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square. + // so we can use it to represent the point at infinity. + if (c0 == 0 && c1 == 0) { + // Point at infinity as encoded in EIP197. + return (0, 0, 0, 0); + } + bool negate_point = c0 & 1 == 1; + bool hint = c0 & 2 == 2; + x0 = c0 >> 2; + x1 = c1; + if (x0 >= P || x1 >= P) { + // G2 x0 or x1 coefficient not in field. + revert ProofInvalid(); + } + + uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P); + uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P); + uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P); + + y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P); + y1 = negate(addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)); + + // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve. + // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero. + // But y0 or y1 may still independently be zero. + (y0, y1) = sqrt_Fp2(y0, y1, hint); + if (negate_point) { + y0 = negate(y0); + y1 = negate(y1); + } + } + + /// Compute the public input linear combination. + /// @notice Reverts with PublicInputNotInField if the input is not in the field. + /// @notice Computes the multi-scalar-multiplication of the public input + /// elements and the verification key including the constant term. + /// @param input The public inputs. These are elements of the scalar field Fr. + /// @param publicCommitments public inputs generated from pedersen commitments. + /// @param commitments The Pedersen commitments from the proof. + /// @return x The X coordinate of the resulting G1 point. + /// @return y The Y coordinate of the resulting G1 point. + function publicInputMSM( + uint256[6] calldata input, + uint256[1] memory publicCommitments, + uint256[2] memory commitments + ) internal view returns (uint256 x, uint256 y) { + // Note: The ECMUL precompile does not reject unreduced values, so we check this. + // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the + // code-size is in the PUB_ constants. + // ECMUL has input (x, y, scalar) and output (x', y'). + // ECADD has input (x1, y1, x2, y2) and output (x', y'). + // We reduce commitments(if any) with constants as the first point argument to ECADD. + // We call them such that ecmul output is already in the second point + // argument to ECADD so we can have a tight loop. + bool success = true; + assembly ("memory-safe") { + let f := mload(0x40) + let g := add(f, 0x40) + let s + mstore(f, CONSTANT_X) + mstore(add(f, 0x20), CONSTANT_Y) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, commitments, 64, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + mstore(g, PUB_0_X) + mstore(add(g, 0x20), PUB_0_Y) + s := calldataload(input) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + mstore(g, PUB_1_X) + mstore(add(g, 0x20), PUB_1_Y) + s := calldataload(add(input, 32)) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + mstore(g, PUB_2_X) + mstore(add(g, 0x20), PUB_2_Y) + s := calldataload(add(input, 64)) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + mstore(g, PUB_3_X) + mstore(add(g, 0x20), PUB_3_Y) + s := calldataload(add(input, 96)) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + mstore(g, PUB_4_X) + mstore(add(g, 0x20), PUB_4_Y) + s := calldataload(add(input, 128)) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + mstore(g, PUB_5_X) + mstore(add(g, 0x20), PUB_5_Y) + s := calldataload(add(input, 160)) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + mstore(g, PUB_6_X) + mstore(add(g, 0x20), PUB_6_Y) + s := mload(publicCommitments) + mstore(add(g, 0x40), s) + success := and(success, lt(s, R)) + success := and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40)) + success := and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40)) + + x := mload(f) + y := mload(add(f, 0x20)) + } + if (!success) { + // Either Public input not in field, or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert PublicInputNotInField(); + } + } + + /// Compress a proof. + /// @notice Will revert with InvalidProof if the curve points are invalid, + /// but does not verify the proof itself. + /// @param proof The uncompressed Groth16 proof. Elements are in the same order as for + /// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197. + /// @param commitments Pedersen commitments from the proof. + /// @param commitmentPok proof of knowledge for the Pedersen commitments. + /// @return compressed The compressed proof. Elements are in the same order as for + /// verifyCompressedProof. I.e. points (A, B, C) in compressed format. + /// @return compressedCommitments compressed Pedersen commitments from the proof. + /// @return compressedCommitmentPok compressed proof of knowledge for the Pedersen commitments. + function compressProof( + uint256[8] calldata proof, + uint256[2] calldata commitments, + uint256[2] calldata commitmentPok + ) + public + view + returns ( + uint256[4] memory compressed, + uint256[1] memory compressedCommitments, + uint256 compressedCommitmentPok + ) + { + compressed[0] = compress_g1(proof[0], proof[1]); + (compressed[2], compressed[1]) = compress_g2(proof[3], proof[2], proof[5], proof[4]); + compressed[3] = compress_g1(proof[6], proof[7]); + compressedCommitments[0] = compress_g1(commitments[0], commitments[1]); + compressedCommitmentPok = compress_g1(commitmentPok[0], commitmentPok[1]); + } + + /// Verify a Groth16 proof with compressed points. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was successfully verified. + /// @param compressedProof the points (A, B, C) in compressed format + /// matching the output of compressProof. + /// @param compressedCommitments compressed Pedersen commitments from the proof. + /// @param compressedCommitmentPok compressed proof of knowledge for the Pedersen commitments. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyCompressedProof( + uint256[4] calldata compressedProof, + uint256[1] calldata compressedCommitments, + uint256 compressedCommitmentPok, + uint256[6] calldata input + ) public view { + uint256[1] memory publicCommitments; + uint256[2] memory commitments; + uint256[24] memory pairings; + { + (commitments[0], commitments[1]) = decompress_g1(compressedCommitments[0]); + (uint256 Px, uint256 Py) = decompress_g1(compressedCommitmentPok); + + uint256[] memory publicAndCommitmentCommitted; + + publicCommitments[0] = uint256( + sha256( + abi.encodePacked(commitments[0], commitments[1], publicAndCommitmentCommitted) + ) + ) % R; + // Commitments + pairings[0] = commitments[0]; + pairings[1] = commitments[1]; + pairings[2] = PEDERSEN_G_X_1; + pairings[3] = PEDERSEN_G_X_0; + pairings[4] = PEDERSEN_G_Y_1; + pairings[5] = PEDERSEN_G_Y_0; + pairings[6] = Px; + pairings[7] = Py; + pairings[8] = PEDERSEN_GROOTSIGMANEG_X_1; + pairings[9] = PEDERSEN_GROOTSIGMANEG_X_0; + pairings[10] = PEDERSEN_GROOTSIGMANEG_Y_1; + pairings[11] = PEDERSEN_GROOTSIGMANEG_Y_0; + + // Verify pedersen commitments + bool success; + assembly ("memory-safe") { + let f := mload(0x40) + + success := staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x180, f, 0x20) + success := and(success, mload(f)) + } + if (!success) { + revert CommitmentInvalid(); + } + } + + { + (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]); + (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) = + decompress_g2(compressedProof[2], compressedProof[1]); + (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]); + (uint256 Lx, uint256 Ly) = publicInputMSM(input, publicCommitments, commitments); + + // Verify the pairing + // Note: The precompile expects the F2 coefficients in big-endian order. + // Note: The pairing precompile rejects unreduced values, so we won't check that here. + // e(A, B) + pairings[0] = Ax; + pairings[1] = Ay; + pairings[2] = Bx1; + pairings[3] = Bx0; + pairings[4] = By1; + pairings[5] = By0; + // e(C, -δ) + pairings[6] = Cx; + pairings[7] = Cy; + pairings[8] = DELTA_NEG_X_1; + pairings[9] = DELTA_NEG_X_0; + pairings[10] = DELTA_NEG_Y_1; + pairings[11] = DELTA_NEG_Y_0; + // e(α, -β) + pairings[12] = ALPHA_X; + pairings[13] = ALPHA_Y; + pairings[14] = BETA_NEG_X_1; + pairings[15] = BETA_NEG_X_0; + pairings[16] = BETA_NEG_Y_1; + pairings[17] = BETA_NEG_Y_0; + // e(L_pub, -γ) + pairings[18] = Lx; + pairings[19] = Ly; + pairings[20] = GAMMA_NEG_X_1; + pairings[21] = GAMMA_NEG_X_0; + pairings[22] = GAMMA_NEG_Y_1; + pairings[23] = GAMMA_NEG_Y_0; + + // Check pairing equation. + bool success; + uint256[1] memory output; + assembly ("memory-safe") { + success := staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x300, output, 0x20) + } + if (!success || output[0] != 1) { + // Either proof or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert ProofInvalid(); + } + } + } + + /// Verify an uncompressed Groth16 proof. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was successfully verified. + /// @param proof the points (A, B, C) in EIP-197 format matching the output + /// of compressProof. + /// @param commitments the Pedersen commitments from the proof. + /// @param commitmentPok the proof of knowledge for the Pedersen commitments. + /// @param input the public input field elements in the scalar field Fr. + /// Elements must be reduced. + function verifyProof( + uint256[8] calldata proof, + uint256[2] calldata commitments, + uint256[2] calldata commitmentPok, + uint256[6] calldata input + ) public view { + // HashToField + uint256[1] memory publicCommitments; + uint256[] memory publicAndCommitmentCommitted; + + publicCommitments[0] = uint256( + sha256(abi.encodePacked(commitments[0], commitments[1], publicAndCommitmentCommitted)) + ) % R; + + // Verify pedersen commitments + bool success; + assembly ("memory-safe") { + let f := mload(0x40) + + calldatacopy(f, commitments, 0x40) // Copy Commitments + mstore(add(f, 0x40), PEDERSEN_G_X_1) + mstore(add(f, 0x60), PEDERSEN_G_X_0) + mstore(add(f, 0x80), PEDERSEN_G_Y_1) + mstore(add(f, 0xa0), PEDERSEN_G_Y_0) + calldatacopy(add(f, 0xc0), commitmentPok, 0x40) + mstore(add(f, 0x100), PEDERSEN_GROOTSIGMANEG_X_1) + mstore(add(f, 0x120), PEDERSEN_GROOTSIGMANEG_X_0) + mstore(add(f, 0x140), PEDERSEN_GROOTSIGMANEG_Y_1) + mstore(add(f, 0x160), PEDERSEN_GROOTSIGMANEG_Y_0) + + success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x180, f, 0x20) + success := and(success, mload(f)) + } + if (!success) { + revert CommitmentInvalid(); + } + + (uint256 x, uint256 y) = publicInputMSM(input, publicCommitments, commitments); + + // Note: The precompile expects the F2 coefficients in big-endian order. + // Note: The pairing precompile rejects unreduced values, so we won't check that here. + assembly ("memory-safe") { + let f := mload(0x40) // Free memory pointer. + + // Copy points (A, B, C) to memory. They are already in correct encoding. + // This is pairing e(A, B) and G1 of e(C, -δ). + calldatacopy(f, proof, 0x100) + + // Complete e(C, -δ) and write e(α, -β), e(L_pub, -γ) to memory. + // OPT: This could be better done using a single codecopy, but + // Solidity (unlike standalone Yul) doesn't provide a way to + // to do this. + mstore(add(f, 0x100), DELTA_NEG_X_1) + mstore(add(f, 0x120), DELTA_NEG_X_0) + mstore(add(f, 0x140), DELTA_NEG_Y_1) + mstore(add(f, 0x160), DELTA_NEG_Y_0) + mstore(add(f, 0x180), ALPHA_X) + mstore(add(f, 0x1a0), ALPHA_Y) + mstore(add(f, 0x1c0), BETA_NEG_X_1) + mstore(add(f, 0x1e0), BETA_NEG_X_0) + mstore(add(f, 0x200), BETA_NEG_Y_1) + mstore(add(f, 0x220), BETA_NEG_Y_0) + mstore(add(f, 0x240), x) + mstore(add(f, 0x260), y) + mstore(add(f, 0x280), GAMMA_NEG_X_1) + mstore(add(f, 0x2a0), GAMMA_NEG_X_0) + mstore(add(f, 0x2c0), GAMMA_NEG_Y_1) + mstore(add(f, 0x2e0), GAMMA_NEG_Y_0) + + // Check pairing equation. + success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x300, f, 0x20) + // Also check returned value (both are either 1 or 0). + success := and(success, mload(f)) + } + if (!success) { + // Either proof or verification key invalid. + // We assume the contract is correctly generated, so the verification key is valid. + revert ProofInvalid(); + } + } +} diff --git a/src/test/WorldIDTest.sol b/src/test/WorldIDTest.sol index 0db2fb0..95fc899 100644 --- a/src/test/WorldIDTest.sol +++ b/src/test/WorldIDTest.sol @@ -14,7 +14,7 @@ contract WorldIDTest is Test { /// TEST DATA /// /////////////////////////////////////////////////////////////////////////////// - Vm internal hevm = Vm(HEVM_ADDRESS); + Vm internal hevm = Vm(VM_ADDRESS); address internal nullAddress = address(0x0); address internal thisAddress = address(this); diff --git a/src/test/identity-manager/WorldIDIdentityManagerCalculation.t.sol b/src/test/identity-manager/WorldIDIdentityManagerCalculation.t.sol index f9f4c1f..f8637d1 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerCalculation.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerCalculation.t.sol @@ -32,7 +32,7 @@ contract WorldIDIdentityManagerCalculation is WorldIDIdentityManagerTest { vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.calculateIdentityRegistrationInputHash( + managerImplV2.calculateIdentityRegistrationInputHash( startIndex, insertionPreRoot, insertionPostRoot, identityCommitments ); } @@ -57,7 +57,7 @@ contract WorldIDIdentityManagerCalculation is WorldIDIdentityManagerTest { vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.calculateIdentityDeletionInputHash( + managerImplV2.calculateIdentityDeletionInputHash( packedDeletionIndices, deletionPreRoot, deletionPostRoot, deletionBatchSize ); } diff --git a/src/test/identity-manager/WorldIDIdentityManagerConstruction.t.sol b/src/test/identity-manager/WorldIDIdentityManagerConstruction.t.sol index 385a57c..2b20e7a 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerConstruction.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerConstruction.t.sol @@ -45,18 +45,18 @@ contract WorldIDIdentityManagerConstruction is WorldIDIdentityManagerTest { ); // Test - identityManager = new IdentityManager(address(managerImpl), callData); + identityManager = new IdentityManager(address(managerImplV2), callData); identityManagerAddress = address(identityManager); // creates Manager Impl V2, which will be used for tests - managerImpl = new ManagerImpl(); - managerImplAddress = address(managerImpl); + managerImplV2 = new ManagerImpl(); + managerImplV2Address = address(managerImplV2); bytes memory initCallV2 = abi.encodeCall(ManagerImpl.initializeV2, (defaultDeletionVerifiers)); bytes memory upgradeCall = abi.encodeCall( - UUPSUpgradeable.upgradeToAndCall, (address(managerImplAddress), initCallV2) + UUPSUpgradeable.upgradeToAndCall, (address(managerImplV2Address), initCallV2) ); // Test diff --git a/src/test/identity-manager/WorldIDIdentityManagerDataQuery.t.sol b/src/test/identity-manager/WorldIDIdentityManagerDataQuery.t.sol index 2df1cb1..74a107f 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerDataQuery.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerDataQuery.t.sol @@ -7,6 +7,7 @@ import {SemaphoreTreeDepthValidator} from "../../utils/SemaphoreTreeDepthValidat import {SimpleVerify} from "../mock/SimpleVerifier.sol"; import {TypeConverter as TC} from "../utils/TypeConverter.sol"; import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; +import {VerifierLookupTable4844} from "../../data/VerifierLookupTable4844.sol"; import {WorldIDIdentityManagerImplV2 as ManagerImpl} from "../../WorldIDIdentityManagerImplV2.sol"; import {WorldIDIdentityManagerImplV1 as ManagerImplV1} from "../../WorldIDIdentityManagerImplV1.sol"; @@ -24,6 +25,7 @@ contract WorldIDIdentityManagerDataQuery is WorldIDIdentityManagerTest { treeDepth, newPreRoot, defaultInsertVerifiers, + defaultInsertVerifiers4844, defaultDeletionVerifiers, defaultUpdateVerifiers, semaphoreVerifier @@ -47,15 +49,13 @@ contract WorldIDIdentityManagerDataQuery is WorldIDIdentityManagerTest { vm.assume(SimpleVerify.isValidInput(uint256(prf[0]))); vm.assume(newPreRoot != newPostRoot); vm.assume(identities.length <= 1000); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identities.length])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([identities.length])); makeNewIdentityManager( treeDepth, newPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -88,15 +88,13 @@ contract WorldIDIdentityManagerDataQuery is WorldIDIdentityManagerTest { vm.assume(newPreRoot != newPostRoot); vm.assume(SimpleVerify.isValidInput(uint256(prf[0]))); vm.assume(identities.length <= 1000); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identities.length])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([identities.length])); makeNewIdentityManager( treeDepth, newPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -128,7 +126,7 @@ contract WorldIDIdentityManagerDataQuery is WorldIDIdentityManagerTest { // Setup vm.assume(badRoot != initialRoot); bytes memory callData = abi.encodeCall(ManagerImplV1.queryRoot, badRoot); - bytes memory returnData = abi.encode(managerImpl.NO_SUCH_ROOT()); + bytes memory returnData = abi.encode(managerImplV2.NO_SUCH_ROOT()); // Test assertCallSucceedsOn(identityManagerAddress, callData, returnData); @@ -140,7 +138,7 @@ contract WorldIDIdentityManagerDataQuery is WorldIDIdentityManagerTest { vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.queryRoot(initialRoot); + managerImplV2.queryRoot(initialRoot); } /// @notice Checks that it is possible to get the latest root from the contract. @@ -150,6 +148,7 @@ contract WorldIDIdentityManagerDataQuery is WorldIDIdentityManagerTest { treeDepth, actualRoot, defaultInsertVerifiers, + defaultInsertVerifiers4844, defaultDeletionVerifiers, defaultUpdateVerifiers, semaphoreVerifier @@ -167,7 +166,7 @@ contract WorldIDIdentityManagerDataQuery is WorldIDIdentityManagerTest { vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.latestRoot(); + managerImplV2.latestRoot(); } /// @notice Checks that it is possible to get the tree depth the contract was initialized with. @@ -178,6 +177,7 @@ contract WorldIDIdentityManagerDataQuery is WorldIDIdentityManagerTest { actualTreeDepth, insertionPreRoot, defaultInsertVerifiers, + defaultInsertVerifiers4844, defaultDeletionVerifiers, defaultUpdateVerifiers, semaphoreVerifier diff --git a/src/test/identity-manager/WorldIDIdentityManagerGettersSetters.t.sol b/src/test/identity-manager/WorldIDIdentityManagerGettersSetters.t.sol index 921e387..f131d8f 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerGettersSetters.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerGettersSetters.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.21; import {WorldIDIdentityManagerTest} from "./WorldIDIdentityManagerTest.sol"; -import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; import {SemaphoreVerifier} from "src/SemaphoreVerifier.sol"; import {SimpleVerifier, SimpleVerify} from "../mock/SimpleVerifier.sol"; import {TypeConverter as TC} from "../utils/TypeConverter.sol"; @@ -43,17 +42,17 @@ contract WorldIDIdentityManagerGettersSetters is WorldIDIdentityManagerTest { vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.getRegisterIdentitiesVerifierLookupTableAddress(); + managerImplV2.getRegisterIdentitiesVerifierLookupTableAddress(); } /// @notice Checks that it is possible to set the lookup table currently being used to verify /// identity registration proofs. function testCanSetRegisterIdentitiesVerifierLookupTable() public { // Setup - (VerifierLookupTable insertionVerifiers,,) = makeVerifierLookupTables(TC.makeDynArray([40])); - address newVerifiersAddress = address(insertionVerifiers); + (insertVerifiers,,,) = makeVerifierLookupTables(TC.makeDynArray([40])); + address newVerifiersAddress = address(insertVerifiers); bytes memory callData = abi.encodeCall( - ManagerImplV1.setRegisterIdentitiesVerifierLookupTable, (insertionVerifiers) + ManagerImplV1.setRegisterIdentitiesVerifierLookupTable, (insertVerifiers) ); bytes memory checkCallData = abi.encodeCall(ManagerImplV1.getRegisterIdentitiesVerifierLookupTableAddress, ()); @@ -74,9 +73,9 @@ contract WorldIDIdentityManagerGettersSetters is WorldIDIdentityManagerTest { { // Setup vm.assume(notOwner != address(this) && notOwner != address(0x0)); - (VerifierLookupTable insertionVerifiers,,) = makeVerifierLookupTables(TC.makeDynArray([40])); + (insertVerifiers,,,) = makeVerifierLookupTables(TC.makeDynArray([40])); bytes memory callData = abi.encodeCall( - ManagerImplV1.setRegisterIdentitiesVerifierLookupTable, (insertionVerifiers) + ManagerImplV1.setRegisterIdentitiesVerifierLookupTable, (insertVerifiers) ); bytes memory errorData = encodeStringRevert("Ownable: caller is not the owner"); vm.prank(notOwner); @@ -89,11 +88,11 @@ contract WorldIDIdentityManagerGettersSetters is WorldIDIdentityManagerTest { /// identity registration unless called via the proxy. function testCannotSetRegisterIdentitiesVerifierLookupTableUnlessViaProxy() public { // Setup - (VerifierLookupTable insertionVerifiers,,) = makeVerifierLookupTables(TC.makeDynArray([40])); + (insertVerifiers,,,) = makeVerifierLookupTables(TC.makeDynArray([40])); vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.setRegisterIdentitiesVerifierLookupTable(insertionVerifiers); + managerImplV2.setRegisterIdentitiesVerifierLookupTable(insertVerifiers); } /// @notice Checks that it is possible to get the address of the contract currently being used @@ -115,14 +114,14 @@ contract WorldIDIdentityManagerGettersSetters is WorldIDIdentityManagerTest { vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.getDeleteIdentitiesVerifierLookupTableAddress(); + managerImplV2.getDeleteIdentitiesVerifierLookupTableAddress(); } /// @notice Checks that it is possible to set the lookup table currently being used to verify /// identity deletion proofs. function testCanSetDeleteIdentitiesVerifierLookupTable() public { // Setup - (, VerifierLookupTable deletionVerifiers,) = makeVerifierLookupTables(TC.makeDynArray([40])); + (,, deletionVerifiers,) = makeVerifierLookupTables(TC.makeDynArray([40])); address newVerifiersAddress = address(deletionVerifiers); bytes memory callData = abi.encodeCall(ManagerImpl.setDeleteIdentitiesVerifierLookupTable, (deletionVerifiers)); @@ -143,7 +142,7 @@ contract WorldIDIdentityManagerGettersSetters is WorldIDIdentityManagerTest { function testCannotSetDeleteIdentitiesVerifierLookupTableUnlessOwner(address notOwner) public { // Setup vm.assume(notOwner != address(this) && notOwner != address(0x0)); - (, VerifierLookupTable deletionVerifiers,) = makeVerifierLookupTables(TC.makeDynArray([40])); + (,, deletionVerifiers,) = makeVerifierLookupTables(TC.makeDynArray([40])); bytes memory callData = abi.encodeCall( ManagerImplV1.setRegisterIdentitiesVerifierLookupTable, (deletionVerifiers) ); @@ -158,11 +157,11 @@ contract WorldIDIdentityManagerGettersSetters is WorldIDIdentityManagerTest { /// identity deletion unless called via the proxy. function testCannotSetDeleteIdentitiesVerifierLookupTableUnlessViaProxy() public { // Setup - (, VerifierLookupTable deletionVerifiers,) = makeVerifierLookupTables(TC.makeDynArray([40])); + (,, deletionVerifiers,) = makeVerifierLookupTables(TC.makeDynArray([40])); vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.setDeleteIdentitiesVerifierLookupTable(deletionVerifiers); + managerImplV2.setDeleteIdentitiesVerifierLookupTable(deletionVerifiers); } /// @notice Ensures that we can get the address of the semaphore verifier. @@ -181,7 +180,7 @@ contract WorldIDIdentityManagerGettersSetters is WorldIDIdentityManagerTest { vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.getSemaphoreVerifierAddress(); + managerImplV2.getSemaphoreVerifierAddress(); } /// @notice Checks that it is possible to set the contract currently being used to verify @@ -224,7 +223,7 @@ contract WorldIDIdentityManagerGettersSetters is WorldIDIdentityManagerTest { vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.setSemaphoreVerifier(newVerifier); + managerImplV2.setSemaphoreVerifier(newVerifier); } /// @notice Ensures that it's possible to get the root history expiry time. @@ -243,7 +242,7 @@ contract WorldIDIdentityManagerGettersSetters is WorldIDIdentityManagerTest { vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.getRootHistoryExpiry(); + managerImplV2.getRootHistoryExpiry(); } /// @notice Ensures that it is possible to set the root history expiry time. @@ -290,6 +289,6 @@ contract WorldIDIdentityManagerGettersSetters is WorldIDIdentityManagerTest { vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.setRootHistoryExpiry(newExpiry); + managerImplV2.setRootHistoryExpiry(newExpiry); } } diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol index fcdd41c..e055393 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerIdentityDeletion.t.sol @@ -8,6 +8,7 @@ import {SimpleVerifier, SimpleVerify} from "../mock/SimpleVerifier.sol"; import {TypeConverter as TC} from "../utils/TypeConverter.sol"; import {Verifier as TreeVerifier} from "src/test/DeletionTreeVerifier16.sol"; import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; +import {VerifierLookupTable4844} from "../../data/VerifierLookupTable4844.sol"; import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; import {WorldIDIdentityManagerImplV2 as ManagerImpl} from "../../WorldIDIdentityManagerImplV2.sol"; @@ -35,16 +36,14 @@ contract WorldIDIdentityManagerIdentityDeletion is WorldIDIdentityManagerTest { function testDeleteIdentitiesWithCorrectInputsFromKnown() public { // Setup ITreeVerifier actualVerifier = new TreeVerifier(); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([40])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([40])); deletionVerifiers.addVerifier(deletionBatchSize, actualVerifier); makeNewIdentityManager( treeDepth, deletionPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -82,15 +81,13 @@ contract WorldIDIdentityManagerIdentityDeletion is WorldIDIdentityManagerTest { vm.assume(packedDeletionIndices.length <= 125); vm.assume(packedDeletionIndices.length % 4 == 0); vm.assume(identityOperator != nullAddress && identityOperator != thisAddress); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([packedDeletionIndices.length / 4])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([packedDeletionIndices.length / 4])); makeNewIdentityManager( treeDepth, newPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -127,15 +124,13 @@ contract WorldIDIdentityManagerIdentityDeletion is WorldIDIdentityManagerTest { bytes memory secondIndices = abi.encodePacked(uint32(0), uint32(2), uint32(4), uint32(6)); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([deletionBatchSize, secondIndices.length / 4])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([deletionBatchSize, secondIndices.length / 4])); makeNewIdentityManager( treeDepth, newPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -173,17 +168,14 @@ contract WorldIDIdentityManagerIdentityDeletion is WorldIDIdentityManagerTest { vm.assume(newPreRoot != newPostRoot); vm.assume(packedDeletionIndices.length > 4); vm.assume(packedDeletionIndices.length % 4 == 0); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = // the -1 offsets the correct batch size by 1 thus causing the error makeVerifierLookupTables(TC.makeDynArray([(packedDeletionIndices.length / 4) - 1])); makeNewIdentityManager( treeDepth, newPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -211,16 +203,14 @@ contract WorldIDIdentityManagerIdentityDeletion is WorldIDIdentityManagerTest { vm.assume(newPreRoot != newPostRoot); ITreeVerifier actualVerifier = new TreeVerifier(); uint32 indicesLength = uint32(packedDeletionIndices.length / 4); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([70])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([70])); deletionVerifiers.addVerifier(indicesLength, actualVerifier); makeNewIdentityManager( treeDepth, newPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -242,16 +232,14 @@ contract WorldIDIdentityManagerIdentityDeletion is WorldIDIdentityManagerTest { // Setup vm.assume(newPostRoot != deletionPostRoot && newPostRoot < SNARK_SCALAR_FIELD); ITreeVerifier actualVerifier = new TreeVerifier(); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([70])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([70])); deletionVerifiers.addVerifier(deletionBatchSize, actualVerifier); makeNewIdentityManager( treeDepth, deletionPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -299,6 +287,7 @@ contract WorldIDIdentityManagerIdentityDeletion is WorldIDIdentityManagerTest { treeDepth, uint256(currentPreRoot), defaultInsertVerifiers, + defaultInsertVerifiers4844, defaultDeletionVerifiers, defaultUpdateVerifiers, semaphoreVerifier @@ -318,12 +307,12 @@ contract WorldIDIdentityManagerIdentityDeletion is WorldIDIdentityManagerTest { /// @notice Tests that identities can only be deleted through the proxy. function testCannotDelteIdentitiesIfNotViaProxy() public { // Setup - address expectedOwner = managerImpl.owner(); + address expectedOwner = managerImplV2.owner(); vm.expectRevert("Function must be called through delegatecall"); vm.prank(expectedOwner); // Test - managerImpl.deleteIdentities( + managerImplV2.deleteIdentities( deletionProof, packedDeletionIndices, initialRoot, deletionPostRoot ); } diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol index 48e2288..03c8e78 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration.t.sol @@ -9,7 +9,9 @@ import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; import {SimpleVerifier, SimpleVerify} from "../mock/SimpleVerifier.sol"; import {TypeConverter as TC} from "../utils/TypeConverter.sol"; import {Verifier as TreeVerifier} from "src/test/InsertionTreeVerifier16.sol"; +import {Verifier as TreeVerifier4844} from "src/test/InsertionTreeVerifier164844.sol"; import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; +import {VerifierLookupTable4844} from "../../data/VerifierLookupTable4844.sol"; import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; import {WorldIDIdentityManagerImplV2 as ManagerImpl} from "../../WorldIDIdentityManagerImplV2.sol"; @@ -35,16 +37,14 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes function testRegisterIdentitiesWithCorrectInputsFromKnown() public { // Setup ITreeVerifier actualVerifier = new TreeVerifier(); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([40])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([40])); insertVerifiers.addVerifier(identityCommitmentsSize, actualVerifier); makeNewIdentityManager( treeDepth, insertionPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -83,15 +83,13 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes vm.assume(newPreRoot != newPostRoot); vm.assume(identities.length <= 1000); vm.assume(identityOperator != nullAddress && identityOperator != thisAddress); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identities.length])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([identities.length])); makeNewIdentityManager( treeDepth, newPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -130,15 +128,13 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes vm.assume(newPreRoot != newPostRoot); vm.assume(identities.length <= 1000 && identities.length > 0); uint256 secondIdentsLength = identities.length / 2; - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identities.length, secondIdentsLength])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([identities.length, secondIdentsLength])); makeNewIdentityManager( treeDepth, newPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -182,15 +178,13 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes vm.assume(SimpleVerify.isValidInput(uint256(prf[0]))); vm.assume(newPreRoot != newPostRoot); vm.assume(identities.length > 0); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identities.length - 1])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([identities.length - 1])); makeNewIdentityManager( treeDepth, newPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -218,16 +212,14 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes // Setup vm.assume(!SimpleVerify.isValidInput(uint256(prf[0]))); ITreeVerifier actualVerifier = new TreeVerifier(); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([70])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([70])); insertVerifiers.addVerifier(identityCommitments.length, actualVerifier); makeNewIdentityManager( treeDepth, newPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -248,16 +240,14 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes // Setup vm.assume(newStartIndex != startIndex); ITreeVerifier actualVerifier = new TreeVerifier(); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([70])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([70])); insertVerifiers.addVerifier(identityCommitmentsSize, actualVerifier); makeNewIdentityManager( treeDepth, insertionPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -290,16 +280,14 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes uint256[] memory identities = cloneArray(identityCommitments); identities[invalidSlot] = identity; ITreeVerifier actualVerifier = new TreeVerifier(); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([70])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([70])); insertVerifiers.addVerifier(identityCommitmentsSize, actualVerifier); makeNewIdentityManager( treeDepth, insertionPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -319,14 +307,11 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes function testCannotRegisterIdentitiesIfPostRootIncorrect(uint256 newPostRoot) public { // Setup vm.assume(newPostRoot != insertionPostRoot && newPostRoot < SNARK_SCALAR_FIELD); - managerImpl = new ManagerImpl(); - managerImplAddress = address(managerImpl); + managerImplV2 = new ManagerImpl(); + managerImplV2Address = address(managerImplV2); ITreeVerifier actualVerifier = new TreeVerifier(); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([70])); + (insertVerifiers, deletionVerifiers, updateVerifiers,) = + makeVerifierLookupTables(TC.makeDynArray([70])); insertVerifiers.addVerifier(identityCommitmentsSize, actualVerifier); bytes memory callData = abi.encodeCall( @@ -334,13 +319,13 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes (treeDepth, insertionPreRoot, insertVerifiers, updateVerifiers, semaphoreVerifier) ); - identityManager = new IdentityManager(managerImplAddress, callData); + identityManager = new IdentityManager(managerImplV2Address, callData); identityManagerAddress = address(identityManager); // Init V2 bytes memory initCallV2 = abi.encodeCall(ManagerImpl.initializeV2, (deletionVerifiers)); bytes memory upgradeCall = abi.encodeCall( - UUPSUpgradeable.upgradeToAndCall, (address(managerImplAddress), initCallV2) + UUPSUpgradeable.upgradeToAndCall, (address(managerImplV2Address), initCallV2) ); assertCallSucceedsOn(identityManagerAddress, upgradeCall, new bytes(0x0)); @@ -387,6 +372,7 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes treeDepth, uint256(currentPreRoot), defaultInsertVerifiers, + defaultInsertVerifiers4844, defaultDeletionVerifiers, defaultUpdateVerifiers, semaphoreVerifier @@ -412,15 +398,13 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes vm.assume(identitiesLength != 0 && identitiesLength <= 1000); vm.assume(zeroPosition < identitiesLength && zeroPosition > 0); uint256[] memory identities = new uint256[](identitiesLength); - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(TC.makeDynArray([identitiesLength])); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([identitiesLength])); makeNewIdentityManager( treeDepth, initialRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -451,12 +435,12 @@ contract WorldIDIdentityManagerIdentityRegistration is WorldIDIdentityManagerTes /// @notice Tests that identities can only be registered through the proxy. function testCannotRegisterIdentitiesIfNotViaProxy() public { // Setup - address expectedOwner = managerImpl.owner(); + address expectedOwner = managerImplV2.owner(); vm.expectRevert("Function must be called through delegatecall"); vm.prank(expectedOwner); // Test - managerImpl.registerIdentities( + managerImplV2.registerIdentities( insertionProof, initialRoot, startIndex, identityCommitments, insertionPostRoot ); } diff --git a/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration4844.t.sol b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration4844.t.sol new file mode 100644 index 0000000..a866f5b --- /dev/null +++ b/src/test/identity-manager/WorldIDIdentityManagerIdentityRegistration4844.t.sol @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {UUPSUpgradeable} from "contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +import {WorldIDIdentityManagerTest} from "./WorldIDIdentityManagerTest.sol"; + +import {ITreeVerifier4844 as ITreeVerifier} from "../../interfaces/ITreeVerifier4844.sol"; +import {SimpleVerify} from "../mock/SimpleVerifier.sol"; +import {TypeConverter as TC} from "../utils/TypeConverter.sol"; +import {Verifier as TreeVerifier} from "src/test/InsertionTreeVerifier164844.sol"; +import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; +import {VerifierLookupTable4844} from "../../data/VerifierLookupTable4844.sol"; + +import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; +import {WorldIDIdentityManagerImplV2 as ManagerImpl} from "../../WorldIDIdentityManagerImplV2.sol"; +import {WorldIDIdentityManagerImplV1 as ManagerImplV1} from "../../WorldIDIdentityManagerImplV1.sol"; +import {WorldIDIdentityManagerImplV3 as ManagerImplV3} from "../../WorldIDIdentityManagerImplV3.sol"; + +/// @title World ID Identity Manager Identity Registration Tests for EIP-4844 protocol. +/// @notice Contains tests for the WorldID identity manager. +/// @author Worldcoin +/// @dev This test suite tests both the proxy and the functionality of the underlying implementation +/// so as to test everything in the context of how it will be deployed. +contract WorldIDIdentityManagerIdentityRegistration4844 is WorldIDIdentityManagerTest { + /// Taken from SimpleVerifier.sol + event VerifiedProof(uint256 batchSize); + + /// Taken from WorldIDIdentityManagerImplV1.sol + event TreeChanged( + uint256 indexed insertionPreRoot, + ManagerImplV1.TreeChange indexed kind, + uint256 indexed insertionPostRoot + ); + + /// @notice Checks that the proof validates properly with the correct inputs. + function testRegisterIdentitiesWithCorrectInputsFromKnown() public { + // Setup + ITreeVerifier actualVerifier = new TreeVerifier(); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([40])); + insertVerifiers4844.addVerifier(identityCommitmentsSize, actualVerifier); + makeNewIdentityManager( + treeDepth, + insertionPreRoot, + insertVerifiers, + insertVerifiers4844, + deletionVerifiers, + updateVerifiers, + semaphoreVerifier + ); + + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: insertionProof4844, + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgCommitment, + kzgProof: kzgProof, + expectedEvaluation: insertionExpectedEvaluation, + preRoot: insertionPreRoot, + postRoot: insertionPostRoot4844, + inputHash: insertionInputHash4844, + batchSize: uint32(identityCommitmentsSize), + startIndex: startIndex + }); + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + + bytes memory registerCallData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + bytes memory latestRootCallData = abi.encodeCall(ManagerImplV1.latestRoot, ()); + bytes memory queryRootCallData = + abi.encodeCall(ManagerImplV1.queryRoot, (insertionPostRoot4844)); + + // Test + assertCallSucceedsOn(identityManagerAddress, registerCallData); + assertCallSucceedsOn( + identityManagerAddress, latestRootCallData, abi.encode(insertionPostRoot4844) + ); + assertCallSucceedsOn( + identityManagerAddress, + queryRootCallData, + abi.encode(ManagerImplV1.RootInfo(insertionPostRoot4844, 0, true)) + ); + } + + /// @notice Checks that the proof validates properly with correct inputs. + function testRegisterIdentitiesWithCorrectInputs( + uint128[8] memory prf, + uint32 newStartIndex, + uint128 newPreRoot, + uint128 newPostRoot, + uint128[] memory identities, + address identityOperator + ) public { + // Setup + vm.assume(SimpleVerify.isValidInput(uint256(prf[0]))); + vm.assume(newPreRoot != newPostRoot); + vm.assume(identities.length <= 1000); + vm.assume(identityOperator != nullAddress && identityOperator != thisAddress); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([identities.length])); + makeNewIdentityManager( + treeDepth, + newPreRoot, + insertVerifiers, + insertVerifiers4844, + deletionVerifiers, + updateVerifiers, + semaphoreVerifier + ); + + (uint256[] memory preparedIdents, uint256[8] memory actualProof) = + prepareInsertIdentitiesTestCase(identities, prf); + + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: actualProof, + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgCommitment, + kzgProof: kzgProof, + expectedEvaluation: insertionExpectedEvaluation, + preRoot: newPreRoot, + postRoot: newPostRoot, + inputHash: insertionInputHash4844, + batchSize: uint32(preparedIdents.length), + startIndex: newStartIndex + }); + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + bytes memory callData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + bytes memory setupCallData = + abi.encodeCall(ManagerImplV1.setIdentityOperator, identityOperator); + (bool success,) = identityManagerAddress.call(setupCallData); + assert(success); + + // Expect the root to have been sent to the state bridge. + vm.expectEmit(true, true, true, true); + emit TreeChanged(newPreRoot, ManagerImplV1.TreeChange.Insertion, newPostRoot); + vm.prank(identityOperator); + + // Test + assertCallSucceedsOn(identityManagerAddress, callData); + } + + /// @notice Ensures that identity registration selects the correct verifier when registering + /// identities. + function testRegisterIdentitiesSelectsCorrectVerifier( + uint128[8] memory prf, + uint32 newStartIndex, + uint128 newPreRoot, + uint128 newPostRoot, + uint128[] memory identities + ) public { + vm.assume(SimpleVerify.isValidInput(uint256(prf[0]))); + vm.assume(newPreRoot != newPostRoot); + vm.assume(identities.length <= 1000 && identities.length > 0); + uint256 secondIdentsLength = identities.length / 2; + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([identities.length, secondIdentsLength])); + makeNewIdentityManager( + treeDepth, + newPreRoot, + insertVerifiers, + insertVerifiers4844, + deletionVerifiers, + updateVerifiers, + semaphoreVerifier + ); + (uint256[] memory preparedIdents, uint256[8] memory actualProof) = + prepareInsertIdentitiesTestCase(identities, prf); + uint256[] memory secondIdents = new uint256[](secondIdentsLength); + for (uint256 i = 0; i < secondIdentsLength; ++i) { + secondIdents[i] = preparedIdents[i]; + } + + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: actualProof, + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgCommitment, + kzgProof: kzgProof, + expectedEvaluation: insertionExpectedEvaluation, + preRoot: newPreRoot, + postRoot: newPostRoot, + inputHash: insertionInputHash4844, + batchSize: uint32(preparedIdents.length), + startIndex: newStartIndex + }); + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + bytes memory firstCallData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + uint256 secondPostRoot = uint256(newPostRoot) + 1; + params.preRoot = newPostRoot; + params.batchSize = uint32(secondIdentsLength); + params.postRoot = secondPostRoot; + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + bytes memory secondCallData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + vm.expectEmit(true, true, true, true); + emit VerifiedProof(identities.length); + + // Test + assertCallSucceedsOn(identityManagerAddress, firstCallData); + + vm.expectEmit(true, true, true, true); + emit VerifiedProof(identities.length / 2); + + assertCallSucceedsOn(identityManagerAddress, secondCallData); + } + + /// @notice Ensures that the contract reverts if passed a batch size it doesn't know about. + function testCannotRegisterIdentitiesWithInvalidBatchSize( + uint128[8] memory prf, + uint32 newStartIndex, + uint128 newPreRoot, + uint128 newPostRoot, + uint128[] memory identities + ) public { + vm.assume(SimpleVerify.isValidInput(uint256(prf[0]))); + vm.assume(newPreRoot != newPostRoot); + vm.assume(identities.length > 0); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([identities.length - 1])); + makeNewIdentityManager( + treeDepth, + newPreRoot, + insertVerifiers, + insertVerifiers4844, + deletionVerifiers, + updateVerifiers, + semaphoreVerifier + ); + (uint256[] memory preparedIdents, uint256[8] memory actualProof) = + prepareInsertIdentitiesTestCase(identities, prf); + + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: actualProof, + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgCommitment, + kzgProof: kzgProof, + expectedEvaluation: insertionExpectedEvaluation, + preRoot: newPreRoot, + postRoot: newPostRoot, + inputHash: insertionInputHash4844, + batchSize: uint32(preparedIdents.length), + startIndex: newStartIndex + }); + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + bytes memory callData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + bytes memory errorData = + abi.encodeWithSelector(VerifierLookupTable4844.NoSuchVerifier.selector); + + // Test + assertCallFailsOn(identityManagerAddress, callData, errorData); + } + + /// @notice Checks that it reverts if the provided proof is incorrect for the public inputs. + function testCannotRegisterIdentitiesWithIncorrectInputs( + uint128[8] memory prf, + uint32 newStartIndex, + uint128 newPreRoot, + uint128 newPostRoot + ) public { + // Setup + vm.assume(!SimpleVerify.isValidInput(uint256(prf[0]))); + ITreeVerifier actualVerifier = new TreeVerifier(); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([70])); + insertVerifiers4844.addVerifier(identityCommitments.length, actualVerifier); + makeNewIdentityManager( + treeDepth, + newPreRoot, + insertVerifiers, + insertVerifiers4844, + deletionVerifiers, + updateVerifiers, + semaphoreVerifier + ); + + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: insertionProof, + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgCommitment, + kzgProof: kzgProof, + expectedEvaluation: insertionExpectedEvaluation, + preRoot: newPreRoot, + postRoot: newPostRoot, + inputHash: insertionInputHash4844, + batchSize: uint32(identityCommitments.length), + startIndex: newStartIndex + }); + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + bytes memory callData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + bytes memory expectedError = + abi.encodeWithSelector(ManagerImplV1.ProofValidationFailure.selector); + + // Test + assertCallFailsOn(identityManagerAddress, callData, expectedError); + } + + /// @notice Checks that it reverts if the provided start index is incorrect. + function testCannotRegisterIdentitiesIfStartIndexIncorrect(uint32 newStartIndex) public { + // Setup + vm.assume(newStartIndex != startIndex); + ITreeVerifier actualVerifier = new TreeVerifier(); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([70])); + insertVerifiers4844.addVerifier(identityCommitmentsSize, actualVerifier); + makeNewIdentityManager( + treeDepth, + insertionPreRoot, + insertVerifiers, + insertVerifiers4844, + deletionVerifiers, + updateVerifiers, + semaphoreVerifier + ); + + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: insertionProof, + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgCommitment, + kzgProof: kzgProof, + expectedEvaluation: insertionExpectedEvaluation, + preRoot: insertionPreRoot, + postRoot: insertionPostRoot, + inputHash: insertionInputHash4844, + batchSize: uint32(identityCommitments.length), + startIndex: newStartIndex + }); + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + bytes memory registerCallData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + bytes memory expectedError = + abi.encodeWithSelector(ManagerImplV1.ProofValidationFailure.selector); + + // Test + assertCallFailsOn(identityManagerAddress, registerCallData, expectedError); + } + + /// @notice Checks that it reverts if the provided post root is incorrect. + function testCannotRegisterIdentitiesIfPostRootIncorrect(uint256 newPostRoot) public { + // Setup + vm.assume(newPostRoot != insertionPostRoot && newPostRoot < SNARK_SCALAR_FIELD); + managerImplV2 = new ManagerImpl(); + managerImplV2Address = address(managerImplV2); + ITreeVerifier actualVerifier = new TreeVerifier(); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([70])); + insertVerifiers4844.addVerifier(identityCommitmentsSize, actualVerifier); + + bytes memory callData = abi.encodeCall( + ManagerImplV1.initialize, + (treeDepth, insertionPreRoot, insertVerifiers, updateVerifiers, semaphoreVerifier) + ); + + identityManager = new IdentityManager(managerImplV2Address, callData); + identityManagerAddress = address(identityManager); + + // Init V2 + bytes memory initCallV2 = abi.encodeCall(ManagerImpl.initializeV2, (deletionVerifiers)); + bytes memory upgradeCall = abi.encodeCall( + UUPSUpgradeable.upgradeToAndCall, (address(managerImplV2Address), initCallV2) + ); + assertCallSucceedsOn(identityManagerAddress, upgradeCall, new bytes(0x0)); + + // Init V3 + managerImplV3 = new ManagerImplV3(); + managerImplV3Address = address(managerImplV3); + bytes memory initCallV3 = abi.encodeCall(managerImplV3.initializeV3, (insertVerifiers4844)); + bytes memory upgradeCallV3 = abi.encodeCall( + UUPSUpgradeable.upgradeToAndCall, (address(managerImplV3Address), initCallV3) + ); + assertCallSucceedsOn(identityManagerAddress, upgradeCallV3, new bytes(0x0)); + + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: insertionProof, + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgCommitment, + kzgProof: kzgProof, + expectedEvaluation: insertionExpectedEvaluation, + preRoot: insertionPreRoot, + postRoot: newPostRoot, + inputHash: insertionInputHash4844, + batchSize: uint32(identityCommitments.length), + startIndex: startIndex + }); + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + bytes memory registerCallData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + bytes memory expectedError = + abi.encodeWithSelector(ManagerImplV1.ProofValidationFailure.selector); + + // Test + assertCallFailsOn(identityManagerAddress, registerCallData, expectedError); + } + + /// @notice Tests that it reverts if an attempt is made to register identities as an address + /// that is not the identity operator address. + function testCannotRegisterIdentitiesAsNonIdentityOperator(address nonOperator) public { + // Setup + vm.assume(nonOperator != address(this) && nonOperator != address(0x0)); + + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: insertionProof, + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgCommitment, + kzgProof: kzgProof, + expectedEvaluation: insertionExpectedEvaluation, + preRoot: insertionPreRoot, + postRoot: insertionPostRoot, + inputHash: insertionInputHash4844, + batchSize: uint32(identityCommitments.length), + startIndex: startIndex + }); + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + bytes memory callData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + bytes memory errorData = + abi.encodeWithSelector(ManagerImplV1.Unauthorized.selector, nonOperator); + vm.prank(nonOperator); + + // Test + assertCallFailsOn(identityManagerAddress, callData, errorData); + } + + /// @notice Tests that it reverts if an attempt is made to register identities with an outdated + /// root. + function testCannotRegisterIdentitiesWithOutdatedRoot( + uint256 currentPreRoot, + uint256 actualRoot + ) public { + // Setup + vm.assume( + currentPreRoot != actualRoot && currentPreRoot < SNARK_SCALAR_FIELD + && actualRoot < SNARK_SCALAR_FIELD + ); + makeNewIdentityManager( + treeDepth, + uint256(currentPreRoot), + defaultInsertVerifiers, + defaultInsertVerifiers4844, + defaultDeletionVerifiers, + defaultUpdateVerifiers, + semaphoreVerifier + ); + + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: insertionProof, + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgCommitment, + kzgProof: kzgProof, + expectedEvaluation: insertionExpectedEvaluation, + preRoot: actualRoot, + postRoot: insertionPostRoot, + inputHash: insertionInputHash4844, + batchSize: uint32(identityCommitments.length), + startIndex: startIndex + }); + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + bytes memory callData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + bytes memory expectedError = abi.encodeWithSelector( + ManagerImplV1.NotLatestRoot.selector, actualRoot, uint256(currentPreRoot) + ); + + // Test + assertCallFailsOn(identityManagerAddress, callData, expectedError); + } + + /// @notice Tests that runs of zeroes are accepted by the `registerIdentities4844` function as valid + /// arrays of identity commitments. + function testRegisterIdentitiesWithRunsOfZeroes(uint8 identitiesLength, uint8 zeroPosition) + public + { + // Setup + vm.assume(identitiesLength != 0 && identitiesLength <= 1000); + vm.assume(zeroPosition < identitiesLength && zeroPosition > 0); + uint256[] memory identities = new uint256[](identitiesLength); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([identitiesLength])); + makeNewIdentityManager( + treeDepth, + initialRoot, + insertVerifiers, + insertVerifiers4844, + deletionVerifiers, + updateVerifiers, + semaphoreVerifier + ); + + for (uint256 i = 0; i < zeroPosition; ++i) { + identities[i] = i + 1; + } + for (uint256 i = zeroPosition; i < identitiesLength; ++i) { + identities[i] = 0x0; + } + + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: [uint256(2), 1, 3, 4, 5, 6, 7, 9], + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgCommitment, + kzgProof: kzgProof, + expectedEvaluation: insertionExpectedEvaluation, + preRoot: initialRoot, + postRoot: insertionPostRoot, + inputHash: insertionInputHash4844, + batchSize: uint32(identities.length), + startIndex: startIndex + }); + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + bytes memory callData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + // Test + assertCallSucceedsOn(identityManagerAddress, callData, new bytes(0)); + } + + /// @notice Tests that identities can only be registered through the proxy. + function testCannotRegisterIdentitiesIfNotViaProxy() public { + // Setup + address expectedOwner = managerImplV3.owner(); + vm.expectRevert("Function must be called through delegatecall"); + vm.prank(expectedOwner); + + // Test + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: insertionProof, + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgCommitment, + kzgProof: kzgProof, + expectedEvaluation: insertionExpectedEvaluation, + preRoot: initialRoot, + postRoot: insertionPostRoot, + inputHash: insertionInputHash4844, + batchSize: uint32(identityCommitments.length), + startIndex: startIndex + }); + + managerImplV3.registerIdentities4844(params); + } + + /// @notice Checks that the transaction fails if KZG proof cannot be verified. + function testRegisterIdentitiesWithBadKzgProof() public { + // Setup + ITreeVerifier actualVerifier = new TreeVerifier(); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([40])); + insertVerifiers4844.addVerifier(identityCommitmentsSize, actualVerifier); + makeNewIdentityManager( + treeDepth, + insertionPreRoot, + insertVerifiers, + insertVerifiers4844, + deletionVerifiers, + updateVerifiers, + semaphoreVerifier + ); + + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: insertionProof4844, + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgCommitment, + kzgProof: kzgCommitment, // Intentionally pass something that's not the KZG proof + expectedEvaluation: insertionExpectedEvaluation, + preRoot: insertionPreRoot, + postRoot: insertionPostRoot4844, + inputHash: insertionInputHash4844, + batchSize: uint32(identityCommitmentsSize), + startIndex: startIndex + }); + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + + bytes memory registerCallData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + bytes memory expectedError = + abi.encodeWithSelector(ManagerImplV3.KzgProofVerificationFailed.selector); + + // Test + assertCallFailsOn(identityManagerAddress, registerCallData, expectedError); + } + + /// @notice Checks that the transaction fails if KZG commitment does not match the rest of KZG-related input. + function testRegisterIdentitiesWithBadKzgCommitment() public { + // Setup + ITreeVerifier actualVerifier = new TreeVerifier(); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(TC.makeDynArray([40])); + insertVerifiers4844.addVerifier(identityCommitmentsSize, actualVerifier); + makeNewIdentityManager( + treeDepth, + insertionPreRoot, + insertVerifiers, + insertVerifiers4844, + deletionVerifiers, + updateVerifiers, + semaphoreVerifier + ); + + ManagerImplV3.RegisterIdentities4844Params memory params = ManagerImplV3 + .RegisterIdentities4844Params({ + insertionProof: insertionProof4844, + commitments: commitments, + commitmentPok: commitmentsPok, + kzgCommitment: kzgProof, // Intentionally pass something that's not the KZG commitment + kzgProof: kzgProof, + expectedEvaluation: insertionExpectedEvaluation, + preRoot: insertionPreRoot, + postRoot: insertionPostRoot4844, + inputHash: insertionInputHash4844, + batchSize: uint32(identityCommitmentsSize), + startIndex: startIndex + }); + + // Mock blobhash. This is valid for the next call only. + prepareBlobhash(kzgToVersionedHash(kzgCommitment)); + + bytes memory registerCallData = abi.encodeCall(ManagerImplV3.registerIdentities4844, params); + + bytes memory expectedError = + abi.encodeWithSelector(ManagerImplV3.KzgProofVerificationFailed.selector); + + // Test + assertCallFailsOn(identityManagerAddress, registerCallData, expectedError); + } +} diff --git a/src/test/identity-manager/WorldIDIdentityManagerInitialization.t.sol b/src/test/identity-manager/WorldIDIdentityManagerInitialization.t.sol index 675ad10..684016e 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerInitialization.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerInitialization.t.sol @@ -24,7 +24,7 @@ contract WorldIDIdentityManagerInitialization is WorldIDIdentityManagerTest { function testInitialisation() public { // Setup delete identityManager; - delete managerImpl; + delete managerImplV2; delete managerImplV1; bytes memory V1CallData = abi.encodeCall( @@ -39,7 +39,7 @@ contract WorldIDIdentityManagerInitialization is WorldIDIdentityManagerTest { ); managerImplV1 = new ManagerImplV1(); - managerImplAddress = address(managerImpl); + managerImplV2Address = address(managerImplV2); vm.expectEmit(true, true, true, true); emit Initialized(1); @@ -48,13 +48,13 @@ contract WorldIDIdentityManagerInitialization is WorldIDIdentityManagerTest { identityManagerAddress = address(identityManager); // creates Manager Impl V2, which will be used for tests - managerImpl = new ManagerImpl(); - managerImplAddress = address(managerImpl); + managerImplV2 = new ManagerImpl(); + managerImplV2Address = address(managerImplV2); bytes memory initCallV2 = abi.encodeCall(ManagerImpl.initializeV2, (defaultDeletionVerifiers)); bytes memory upgradeCall = abi.encodeCall( - UUPSUpgradeable.upgradeToAndCall, (address(managerImplAddress), initCallV2) + UUPSUpgradeable.upgradeToAndCall, (address(managerImplV2Address), initCallV2) ); vm.expectEmit(true, true, true, true); @@ -70,7 +70,7 @@ contract WorldIDIdentityManagerInitialization is WorldIDIdentityManagerTest { function testInitialisation2() public { // Setup delete identityManager; - delete managerImpl; + delete managerImplV2; delete managerImplV1; bytes memory V1CallData = abi.encodeCall( @@ -85,12 +85,12 @@ contract WorldIDIdentityManagerInitialization is WorldIDIdentityManagerTest { ); // creates Manager Impl V2, which will be used for tests - managerImpl = new ManagerImpl(); - managerImplAddress = address(managerImpl); + managerImplV2 = new ManagerImpl(); + managerImplV2Address = address(managerImplV2); vm.expectEmit(true, true, true, true); emit Initialized(1); - identityManager = new IdentityManager(managerImplAddress, V1CallData); + identityManager = new IdentityManager(managerImplV2Address, V1CallData); identityManagerAddress = address(identityManager); bytes memory initCallV2 = @@ -145,10 +145,10 @@ contract WorldIDIdentityManagerInitialization is WorldIDIdentityManagerTest { function testCannotPassUnsupportedTreeDepth() public { // Setup delete identityManager; - delete managerImpl; + delete managerImplV2; - managerImpl = new ManagerImpl(); - managerImplAddress = address(managerImpl); + managerImplV2 = new ManagerImpl(); + managerImplV2Address = address(managerImplV2); uint8 unsupportedDepth = 15; bytes memory callData = abi.encodeCall( @@ -165,6 +165,6 @@ contract WorldIDIdentityManagerInitialization is WorldIDIdentityManagerTest { vm.expectRevert(abi.encodeWithSelector(ManagerImplV1.UnsupportedTreeDepth.selector, 15)); // Test - identityManager = new IdentityManager(managerImplAddress, callData); + identityManager = new IdentityManager(managerImplV2Address, callData); } } diff --git a/src/test/identity-manager/WorldIDIdentityManagerOwnershipManagement.t.sol b/src/test/identity-manager/WorldIDIdentityManagerOwnershipManagement.t.sol index 630fb93..79d9982 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerOwnershipManagement.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerOwnershipManagement.t.sol @@ -137,7 +137,7 @@ contract WorldIDIdentityManagerOwnershipManagement is WorldIDIdentityManagerTest vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.identityOperator(); + managerImplV2.identityOperator(); } /// @notice Ensures that it is possible for the owner to set the address of the identity @@ -180,6 +180,6 @@ contract WorldIDIdentityManagerOwnershipManagement is WorldIDIdentityManagerTest vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.setIdentityOperator(newOperator); + managerImplV2.setIdentityOperator(newOperator); } } diff --git a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol index 2de8c13..3860c3b 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerSemaphoreVerification.t.sol @@ -34,6 +34,7 @@ contract WorldIDIdentityManagerSemaphoreVerification is WorldIDIdentityManagerTe actualTreeDepth, insertionPreRoot, defaultInsertVerifiers, + defaultInsertVerifiers4844, defaultDeletionVerifiers, defaultUpdateVerifiers, actualSemaphoreVerifier @@ -63,6 +64,7 @@ contract WorldIDIdentityManagerSemaphoreVerification is WorldIDIdentityManagerTe actualTreeDepth, inclusionRoot, defaultInsertVerifiers, + defaultInsertVerifiers4844, defaultDeletionVerifiers, defaultUpdateVerifiers, actualSemaphoreVerifier @@ -86,6 +88,7 @@ contract WorldIDIdentityManagerSemaphoreVerification is WorldIDIdentityManagerTe treeDepth, inclusionRoot, defaultInsertVerifiers, + defaultInsertVerifiers4844, defaultDeletionVerifiers, defaultUpdateVerifiers, actualSemaphoreVerifier diff --git a/src/test/identity-manager/WorldIDIdentityManagerTest.sol b/src/test/identity-manager/WorldIDIdentityManagerTest.sol index fffcf09..f1e2be5 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerTest.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerTest.sol @@ -1,23 +1,26 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.21; +pragma solidity ^0.8.24; import {UUPSUpgradeable} from "contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {WorldIDTest} from "../WorldIDTest.sol"; import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; +import {ITreeVerifier4844} from "../../interfaces/ITreeVerifier4844.sol"; import {ISemaphoreVerifier} from "src/interfaces/ISemaphoreVerifier.sol"; import {IBridge} from "../../interfaces/IBridge.sol"; import {SimpleStateBridge} from "../mock/SimpleStateBridge.sol"; -import {SimpleVerifier, SimpleVerify} from "../mock/SimpleVerifier.sol"; +import {SimpleVerifier, SimpleVerifier4844, SimpleVerify} from "../mock/SimpleVerifier.sol"; import {UnimplementedTreeVerifier} from "../../utils/UnimplementedTreeVerifier.sol"; import {SemaphoreVerifier} from "src/SemaphoreVerifier.sol"; import {VerifierLookupTable} from "../../data/VerifierLookupTable.sol"; +import {VerifierLookupTable4844} from "../../data/VerifierLookupTable4844.sol"; import {WorldIDIdentityManager as IdentityManager} from "../../WorldIDIdentityManager.sol"; import {WorldIDIdentityManagerImplV1 as ManagerImplV1} from "../../WorldIDIdentityManagerImplV1.sol"; import {WorldIDIdentityManagerImplV2 as ManagerImpl} from "../../WorldIDIdentityManagerImplV2.sol"; +import {WorldIDIdentityManagerImplV3 as ManagerImplV3} from "../../WorldIDIdentityManagerImplV3.sol"; /// @title World ID Identity Manager Test. /// @notice Contains tests for the WorldID identity manager. @@ -30,18 +33,23 @@ contract WorldIDIdentityManagerTest is WorldIDTest { /////////////////////////////////////////////////////////////////////////////// IdentityManager internal identityManager; + // V3 + ManagerImplV3 internal managerImplV3; // V2 - ManagerImpl internal managerImpl; + ManagerImpl internal managerImplV2; // V1 ManagerImplV1 internal managerImplV1; ITreeVerifier internal treeVerifier; + ITreeVerifier4844 internal treeVerifier4844; uint256 internal initialRoot = 0x0; uint8 internal treeDepth = 16; address internal identityManagerAddress; + // V3 + address internal managerImplV3Address; // V2 - address internal managerImplAddress; + address internal managerImplV2Address; // V1 address internal managerImplV1Address; @@ -65,6 +73,23 @@ contract WorldIDIdentityManagerTest is WorldIDTest { uint256 identityCommitmentsSize = 3; uint256[8] insertionProof; + /////////////////////////////////////////////////////////////////// + /// 4844 INSERTION /// + /////////////////////////////////////////////////////////////////// + /// @dev generated using `./gnark-mbu gen-test-params --mode insertion --tree-depth 16 --batch-size 3 | ./gnark-mbu prove --mode insertion --keys-file test_insertion.ps` + bytes32 internal constant insertionInputHash4844 = + 0x14a24bedc17b5596c60da74552640bd130d41d96b8c587dcadcf23217399e17b; + uint256 internal constant insertionExpectedEvaluation = + 0x3d5d4a7d6098f2147ed77be69d93179e6179479b8771c2554e5404c06f836408; + uint256 internal constant insertionPostRoot4844 = + 0x0c3f30b0604dae9a378e2bf62826bf5a772e9ad745df6f8c8256dff351fecee8; + + uint256[8] insertionProof4844; + uint256[2] commitments; + uint256[2] commitmentsPok; + uint128[3] kzgCommitment; + uint128[3] kzgProof; + /////////////////////////////////////////////////////////////////// /// DELETION /// /////////////////////////////////////////////////////////////////// @@ -113,8 +138,13 @@ contract WorldIDIdentityManagerTest is WorldIDTest { // Verifiers uint256 initialBatchSize = 30; VerifierLookupTable internal defaultInsertVerifiers; + VerifierLookupTable4844 internal defaultInsertVerifiers4844; VerifierLookupTable internal defaultDeletionVerifiers; VerifierLookupTable internal defaultUpdateVerifiers; + VerifierLookupTable insertVerifiers; + VerifierLookupTable deletionVerifiers; + VerifierLookupTable updateVerifiers; + VerifierLookupTable4844 insertVerifiers4844; /////////////////////////////////////////////////////////////////////////////// /// TEST ORCHESTRATION /// @@ -143,6 +173,39 @@ contract WorldIDIdentityManagerTest is WorldIDTest { 0x24830332559eada283d4473b17091b239443e75e9e09f0ebce8e72c235ee665d ]; + insertionProof4844 = [ + 0x18dba02648df62914fe9c6dba182c73480253ed22b383c4ae7ead51152e73300, + 0x0660cf8023a5785e930e0333864ed17c8641a559e7bad817af736f6648a76447, + 0x2662090884185d3f910ce62dadf005a278226d877f41d3c52bd1d6b4a91aa2be, + 0x18a279bd46da024aa71cc7f64c396b3c64a6f13a1cf5fc443ad916ac93478b4d, + 0x0fdd92b46d74766433d3a501da207f8dfb16e4d74fe2a6dcad008f2e656f8842, + 0x2212ff3545056108d1162467172c368a89614ad29469f4d71f02e4ebcb6eb3ac, + 0x041dbe374440a1a1acdef5bc7d204ce3b20e4d6fbd41b41b787896e51ed023e9, + 0x047eddec1fc18e112fe15ce861484e8309f0605260e063c9591a6e0450934c80 + ]; + + commitments = [ + 0x04dd4ea218ac1d6b85f5d8ffb3007ad0c029302d1af96f0830ade252ccba5b98, + 0x18702f80829840758f18e3a9e624a8d049944dcc494bf260f8e0f9047cebf027 + ]; + + commitmentsPok = [ + 0x0ab698df05861ae9048ba5c388857fc32a0db801ab3b8bfc4c9b298819da6a66, + 0x085221b73b2a59518c04f1f6f41a7879637cf9f984ed4e05bd28e6507fb67614 + ]; + + kzgProof = [ + 0x925d42714da54a935f209022d256986a, + 0x2b545ab39f127832297a492ed5875be9, + 0x1a983b57c0639403a38ad7d24e0095bc + ]; + + kzgCommitment = [ + 0xb422b2e3bf75a087b84d8086fd35b8a2, + 0x299a559c92ef938fd63d6e6009b74bb9, + 0x47670537338c47c4472f9be9886b65ac + ]; + // Create the deletion proof term. // output from semaphore-mtb prove in src/test/data/DeletionProof.json /// @dev test_deletion.ps is generated using semaphore-mtb: `./gnark-mbu setup --mode deletion --batch-size 8 --tree-depth 16 --output test_deletion.ps` @@ -187,10 +250,14 @@ contract WorldIDIdentityManagerTest is WorldIDTest { defaultUpdateVerifiers.addVerifier(initialBatchSize, treeVerifier); defaultDeletionVerifiers = new VerifierLookupTable(); defaultDeletionVerifiers.addVerifier(initialBatchSize, treeVerifier); + treeVerifier4844 = new SimpleVerifier4844(initialBatchSize); + defaultInsertVerifiers4844 = new VerifierLookupTable4844(); + defaultInsertVerifiers4844.addVerifier(initialBatchSize, treeVerifier4844); makeNewIdentityManager( treeDepth, initialRoot, defaultInsertVerifiers, + defaultInsertVerifiers4844, defaultDeletionVerifiers, defaultUpdateVerifiers, semaphoreVerifier @@ -198,8 +265,9 @@ contract WorldIDIdentityManagerTest is WorldIDTest { hevm.label(address(this), "Sender"); hevm.label(identityManagerAddress, "IdentityManager"); - hevm.label(managerImplAddress, "ManagerImplementation"); hevm.label(managerImplV1Address, "ManagerImplementationV1"); + hevm.label(managerImplV1Address, "ManagerImplementationV2"); + hevm.label(managerImplV3Address, "ManagerImplementationV3"); } /////////////////////////////////////////////////////////////////////////////// @@ -210,45 +278,53 @@ contract WorldIDIdentityManagerTest is WorldIDTest { /// @dev It is initialised in the globals. /// /// @param actualPreRoot The pre-root to use. - /// @param insertVerifiers The insertion verifier lookup table. - /// @param updateVerifiers The udpate verifier lookup table. - /// @param actualSemaphoreVerifier The Semaphore verifier instance to use. + /// @param insertVer The insertion verifier lookup table. + /// @param insertVer4844 The insertion verifier lookup table for EIP-4844 proofs. + /// @param updateVer The update verifier lookup table. + /// @param actualSemaphoreVer The Semaphore verifier instance to use. function makeNewIdentityManager( uint8 actualTreeDepth, uint256 actualPreRoot, - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers, - ISemaphoreVerifier actualSemaphoreVerifier + VerifierLookupTable insertVer, + VerifierLookupTable4844 insertVer4844, + VerifierLookupTable deletionVer, + VerifierLookupTable updateVer, + ISemaphoreVerifier actualSemaphoreVer ) public { managerImplV1 = new ManagerImplV1(); managerImplV1Address = address(managerImplV1); bytes memory initCallData = abi.encodeCall( ManagerImplV1.initialize, - ( - actualTreeDepth, - actualPreRoot, - insertVerifiers, - updateVerifiers, - actualSemaphoreVerifier - ) + (actualTreeDepth, actualPreRoot, insertVer, updateVer, actualSemaphoreVer) ); identityManager = new IdentityManager(managerImplV1Address, initCallData); identityManagerAddress = address(identityManager); // creates Manager Impl V2, which will be used for tests - managerImpl = new ManagerImpl(); - managerImplAddress = address(managerImpl); + managerImplV2 = new ManagerImpl(); + managerImplV2Address = address(managerImplV2); - bytes memory initCallV2 = abi.encodeCall(ManagerImpl.initializeV2, (deletionVerifiers)); - bytes memory upgradeCall = abi.encodeCall( - UUPSUpgradeable.upgradeToAndCall, (address(managerImplAddress), initCallV2) + bytes memory initCallV2 = abi.encodeCall(managerImplV2.initializeV2, (deletionVer)); + bytes memory upgradeCallV2 = abi.encodeCall( + UUPSUpgradeable.upgradeToAndCall, (address(managerImplV2Address), initCallV2) ); // Test - assertCallSucceedsOn(identityManagerAddress, upgradeCall, new bytes(0x0)); + assertCallSucceedsOn(identityManagerAddress, upgradeCallV2, new bytes(0x0)); + + // creates Manager Impl V3, which will be used for tests + managerImplV3 = new ManagerImplV3(); + managerImplV3Address = address(managerImplV3); + + bytes memory initCallV3 = abi.encodeCall(managerImplV3.initializeV3, (insertVer4844)); + bytes memory upgradeCallV3 = abi.encodeCall( + UUPSUpgradeable.upgradeToAndCall, (address(managerImplV3Address), initCallV3) + ); + + // Test + assertCallSucceedsOn(identityManagerAddress, upgradeCallV3, new bytes(0x0)); } /// @notice Initialises a new identity manager using the provided information. @@ -261,11 +337,8 @@ contract WorldIDIdentityManagerTest is WorldIDTest { /// @custom:reverts string If any batch size exceeds 1000. /// @custom:reverts string If `batchSizes` is empty. function makeNewIdentityManager(uint256 actualPreRoot, uint256[] calldata batchSizes) public { - ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers - ) = makeVerifierLookupTables(batchSizes); + (insertVerifiers, deletionVerifiers, updateVerifiers, insertVerifiers4844) = + makeVerifierLookupTables(batchSizes); defaultInsertVerifiers = insertVerifiers; defaultDeletionVerifiers = deletionVerifiers; defaultUpdateVerifiers = updateVerifiers; @@ -275,6 +348,7 @@ contract WorldIDIdentityManagerTest is WorldIDTest { treeDepth, actualPreRoot, insertVerifiers, + insertVerifiers4844, deletionVerifiers, updateVerifiers, semaphoreVerifier @@ -286,9 +360,10 @@ contract WorldIDIdentityManagerTest is WorldIDTest { /// @param batchSizes The batch sizes to create verifiers for. Verifiers will be created for /// both insertions and updates. Must be non-empty and contain no duplicates. /// - /// @return insertVerifiers The insertion verifier lookup table. - /// @return deletionVerifiers The deletion verifier lookup table. - /// @return updateVerifiers The update verifier lookup table. + /// @return insertVer The insertion verifier lookup table. + /// @return deletionVer The deletion verifier lookup table. + /// @return updateVer The update verifier lookup table. + /// @return insertVer4844 The insertion verifier lookup table for EIP-4844 proofs. /// /// @custom:reverts VerifierExists If `batchSizes` contains a duplicate. /// @custom:reverts string If any batch size exceeds 1000. @@ -296,9 +371,10 @@ contract WorldIDIdentityManagerTest is WorldIDTest { function makeVerifierLookupTables(uint256[] memory batchSizes) public returns ( - VerifierLookupTable insertVerifiers, - VerifierLookupTable deletionVerifiers, - VerifierLookupTable updateVerifiers + VerifierLookupTable insertVer, + VerifierLookupTable deletionVer, + VerifierLookupTable updateVer, + VerifierLookupTable4844 insertVer4844 ) { // Construct the verifier LUTs from the provided `batchSizes` info. @@ -308,9 +384,10 @@ contract WorldIDIdentityManagerTest is WorldIDTest { if (batchSizes[0] > 1000) { revert("batch size greater than 1000."); } - insertVerifiers = new VerifierLookupTable(); - deletionVerifiers = new VerifierLookupTable(); - updateVerifiers = new VerifierLookupTable(); + insertVer = new VerifierLookupTable(); + deletionVer = new VerifierLookupTable(); + updateVer = new VerifierLookupTable(); + insertVer4844 = new VerifierLookupTable4844(); for (uint256 i = 0; i < batchSizes.length; ++i) { uint256 batchSize = batchSizes[i]; if (batchSize > 1000) { @@ -318,18 +395,21 @@ contract WorldIDIdentityManagerTest is WorldIDTest { } ITreeVerifier batchVerifier = new SimpleVerifier(batchSize); - insertVerifiers.addVerifier(batchSize, batchVerifier); - deletionVerifiers.addVerifier(batchSize, batchVerifier); - updateVerifiers.addVerifier(batchSize, batchVerifier); + insertVer.addVerifier(batchSize, batchVerifier); + deletionVer.addVerifier(batchSize, batchVerifier); + updateVer.addVerifier(batchSize, batchVerifier); + + ITreeVerifier4844 batchVerifier4844 = new SimpleVerifier4844(batchSize); + insertVer4844.addVerifier(batchSize, batchVerifier4844); } } /// @notice Creates a new identity manager without initializing the delegate. /// @dev It is constructed in the globals. function makeUninitIdentityManager() public { - managerImpl = new ManagerImpl(); - managerImplAddress = address(managerImpl); - identityManager = new IdentityManager(managerImplAddress, new bytes(0x0)); + managerImplV2 = new ManagerImpl(); + managerImplV2Address = address(managerImplV2); + identityManager = new IdentityManager(managerImplV2Address, new bytes(0x0)); identityManagerAddress = address(identityManager); } @@ -379,6 +459,36 @@ contract WorldIDIdentityManagerTest is WorldIDTest { actualProof = [uint256(prf[0]), prf[1], prf[2], prf[3], prf[4], prf[5], prf[6], prf[7]]; } + bytes1 constant VERSIONED_HASH_VERSION_KZG = 0x01; + + /// @notice Convert a KZG commitment to a versioned hash as per EIP-4844. + /// Implementation as per https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md#helpers + /// @param commitment KZG commitment split to 3 128-bit words. + /// @return versioned hash in the form of a 32-byte word. + function kzgToVersionedHash(uint128[3] memory commitment) public pure returns (bytes32) { + bytes memory commitmentBytes = abi.encodePacked(commitment[0], commitment[1], commitment[2]); + bytes32 hash = sha256(commitmentBytes); + + bytes memory truncatedHash = new bytes(31); + for (uint256 i = 0; i < 31; i++) { + truncatedHash[i] = hash[i + 1]; + } + + return bytes32(abi.encodePacked(VERSIONED_HASH_VERSION_KZG, truncatedHash)); + } + + /// @notice Store the given value as a blobhash to be used in tests. + /// The given value will be stored in the 0th blobhash slot and can be retrieved with `blobhash(0)` + /// convenience wrapper of with the `BLOBHASH` opcode. + /// @dev This function is effective only for the next function call, so prepare blobhash as the very last + /// step before the intended usage. + /// @param value Value to be set as contents of the 0th blobhash slot. + function prepareBlobhash(bytes32 value) public { + bytes32[] memory blobhashes = new bytes32[](1); + blobhashes[0] = value; + vm.blobhashes(blobhashes); + } + /// @notice Prepares a verifier test case. /// @dev This is useful to make property-based fuzz testing work better by requiring less /// constraints on the generated input. diff --git a/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol b/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol index dcb7efe..e64927a 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerUninit.t.sol @@ -123,7 +123,7 @@ contract WorldIDIdentityManagerUninit is WorldIDIdentityManagerTest { function testShouldNotCallSetRegisterIdentitiesVerifierLookupTableWhileUninit() public { // Setup makeUninitIdentityManager(); - (VerifierLookupTable insertVerifiers,,) = makeVerifierLookupTables(TC.makeDynArray([75])); + (insertVerifiers,,,) = makeVerifierLookupTables(TC.makeDynArray([75])); bytes memory callData = abi.encodeCall( ManagerImplV1.setRegisterIdentitiesVerifierLookupTable, (insertVerifiers) ); @@ -153,7 +153,7 @@ contract WorldIDIdentityManagerUninit is WorldIDIdentityManagerTest { function testShouldNotCallSetDeleteIdentitiesVerifierLookupTableWhileUninit() public { // Setup makeUninitIdentityManager(); - (, VerifierLookupTable deletionVerifiers,) = makeVerifierLookupTables(TC.makeDynArray([75])); + (,, deletionVerifiers,) = makeVerifierLookupTables(TC.makeDynArray([75])); bytes memory callData = abi.encodeCall(ManagerImpl.setDeleteIdentitiesVerifierLookupTable, (deletionVerifiers)); bytes memory expectedError = diff --git a/src/test/identity-manager/WorldIDIdentityManagerUpgrade.t.sol b/src/test/identity-manager/WorldIDIdentityManagerUpgrade.t.sol index 012c4d4..fbcf4a8 100644 --- a/src/test/identity-manager/WorldIDIdentityManagerUpgrade.t.sol +++ b/src/test/identity-manager/WorldIDIdentityManagerUpgrade.t.sol @@ -74,6 +74,6 @@ contract WorldIDIdentityManagerUpdate is WorldIDIdentityManagerTest { vm.expectRevert("Function must be called through delegatecall"); // Test - managerImpl.upgradeToAndCall(mockUpgradeAddress, initCall); + managerImplV2.upgradeToAndCall(mockUpgradeAddress, initCall); } } diff --git a/src/test/mock/SimpleVerifier.sol b/src/test/mock/SimpleVerifier.sol index ad4e1d0..02adfe3 100644 --- a/src/test/mock/SimpleVerifier.sol +++ b/src/test/mock/SimpleVerifier.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.21; import {ITreeVerifier} from "../../interfaces/ITreeVerifier.sol"; +import {ITreeVerifier4844} from "../../interfaces/ITreeVerifier4844.sol"; /// @title Simple Verifier /// @author Worldcoin @@ -25,20 +26,35 @@ contract SimpleVerifier is ITreeVerifier { } } -library SimpleVerify { - function isValidInput(uint256 a) public pure returns (bool) { - return a % 2 == 0; +/// @title Simple Verifier for EIP-4844 proofs. +/// @author Worldcoin +/// @notice A dumb verifier to make it easy to fuzz test successes and failures. +contract SimpleVerifier4844 is ITreeVerifier4844 { + uint256 batchSize; + + event VerifiedProof(uint256 batchSize); + + constructor(uint256 _batchSize) { + batchSize = _batchSize; } - function calculateInputHash( - uint32 startIndex, - uint256 preRoot, - uint256 postRoot, - uint256[] calldata identityCommitments - ) public pure returns (bytes32 hash) { - bytes memory bytesToHash = - abi.encodePacked(startIndex, preRoot, postRoot, identityCommitments); + function verifyProof( + uint256[8] memory proof, + uint256[2] memory, + uint256[2] memory, + uint256[6] memory input + ) external { + bool result = proof[0] % 2 == 0; - hash = keccak256(bytesToHash); + input[0] = 0; + if (result) { + emit VerifiedProof(batchSize); + } + } +} + +library SimpleVerify { + function isValidInput(uint256 a) public pure returns (bool) { + return a % 2 == 0; } } diff --git a/src/test/mock/WorldIDIdentityManagerImplMock.sol b/src/test/mock/WorldIDIdentityManagerImplMock.sol index f4a2cd0..af99121 100644 --- a/src/test/mock/WorldIDIdentityManagerImplMock.sol +++ b/src/test/mock/WorldIDIdentityManagerImplMock.sol @@ -13,7 +13,8 @@ contract WorldIDIdentityManagerImplMock is WorldIDIdentityManagerImplV1 { } /// @notice Used to initialize the new things in the upgraded contract. - function initialize(uint32 data) public virtual reinitializer(3) { + /// The reinitializer value is high not to conflict with subsequent implementations. + function initialize(uint32 data) public virtual reinitializer(255) { _someMoreData = data; } diff --git a/src/utils/UnimplementedTreeVerifier4844.sol b/src/utils/UnimplementedTreeVerifier4844.sol new file mode 100644 index 0000000..b105348 --- /dev/null +++ b/src/utils/UnimplementedTreeVerifier4844.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {ITreeVerifier4844 as ITreeVerifier} from "../interfaces/ITreeVerifier4844.sol"; + +/// @title Unimplemented Tree Verifier +/// @author Worldcoin +/// @notice A tree verifier instance that will always revert. +/// @dev This verifier is used as the default implementation for the update and remove endpoints in +/// the WorldID identity manager. We do not currently have ZK circuit designs for these +/// endpoints, but having the contract portion already implemented makes it easier for the +/// future where those will work. +contract UnimplementedTreeVerifier is ITreeVerifier { + /// @notice Thrown when an operation is not supported. + error UnsupportedOperation(); + + /// Verify an uncompressed Groth16 proof. + /// @notice Reverts with InvalidProof if the proof is invalid or + /// with PublicInputNotInField the public input is not reduced. + /// @notice There is no return value. If the function does not revert, the + /// proof was successfully verified. + function verifyProof( + uint256[8] calldata, + uint256[2] calldata, + uint256[2] calldata, + uint256[6] calldata + ) public pure { + revert UnsupportedOperation(); + } +}