BN254CertificateVerifier

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/contracts/multichain/BN254CertificateVerifier.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";

import "../libraries/BN254.sol";
import "../libraries/BN254SignatureVerifier.sol";
import "../libraries/Merkle.sol";
import "../libraries/OperatorSetLib.sol";
import "../mixins/SemVerMixin.sol";
import "../mixins/LeafCalculatorMixin.sol";
import "./BN254CertificateVerifierStorage.sol";

/**
 * @title BN254CertificateVerifier
 * @notice Singleton verifier for BN254 certificates across multiple operator sets
 * @dev This contract uses BN254 curves for signature verification and
 *      caches operator information for efficient verification
 */
contract BN254CertificateVerifier is
    Initializable,
    BN254CertificateVerifierStorage,
    SemVerMixin,
    LeafCalculatorMixin
{
    using Merkle for bytes;
    using BN254 for BN254.G1Point;

    /**
     * @notice Struct to hold verification context and reduce stack depth
     */
    struct VerificationContext {
        bytes32 operatorSetKey;
        BN254OperatorSetInfo operatorSetInfo;
        uint256[] totalSignedStakeWeights;
        BN254.G1Point nonSignerApk;
    }

    /**
     * @notice Restricts access to the operator table updater
     */
    modifier onlyTableUpdater() {
        require(msg.sender == address(operatorTableUpdater), OnlyTableUpdater());
        _;
    }

    /**
     * @notice Constructor for the certificate verifier
     * @dev Disables initializers to prevent implementation initialization
     * @param _operatorTableUpdater Address authorized to update operator tables
     * @param _version The semantic version of the contract
     */
    constructor(
        IOperatorTableUpdater _operatorTableUpdater,
        string memory _version
    ) BN254CertificateVerifierStorage(_operatorTableUpdater) SemVerMixin(_version) {
        _disableInitializers();
    }

    /**
     *
     *                         EXTERNAL FUNCTIONS
     *
     */

    ///@inheritdoc IBN254CertificateVerifier
    function updateOperatorTable(
        OperatorSet calldata operatorSet,
        uint32 referenceTimestamp,
        BN254OperatorSetInfo memory operatorSetInfo,
        OperatorSetConfig calldata operatorSetConfig
    ) external onlyTableUpdater {
        bytes32 operatorSetKey = operatorSet.key();

        // Validate that the new timestamp is greater than the latest reference timestamp
        require(referenceTimestamp > _latestReferenceTimestamps[operatorSetKey], TableUpdateStale());

        // Store the operator set info
        _operatorSetInfos[operatorSetKey][referenceTimestamp] = operatorSetInfo;
        _latestReferenceTimestamps[operatorSetKey] = referenceTimestamp;
        _operatorSetOwners[operatorSetKey] = operatorSetConfig.owner;
        _maxStalenessPeriods[operatorSetKey] = operatorSetConfig.maxStalenessPeriod;
        _referenceTimestampsSet[operatorSetKey][referenceTimestamp] = true;

        emit TableUpdated(operatorSet, referenceTimestamp, operatorSetInfo);
    }

    ///@inheritdoc IBN254CertificateVerifier
    function verifyCertificate(
        OperatorSet memory operatorSet,
        BN254Certificate memory cert
    ) external returns (uint256[] memory totalSignedStakeWeights) {
        return _verifyCertificate(operatorSet, cert);
    }

    ///@inheritdoc IBN254CertificateVerifier
    function verifyCertificateProportion(
        OperatorSet memory operatorSet,
        BN254Certificate memory cert,
        uint16[] memory totalStakeProportionThresholds
    ) external returns (bool) {
        uint256[] memory totalSignedStakeWeights = _verifyCertificate(operatorSet, cert);

        bytes32 operatorSetKey = operatorSet.key();
        BN254OperatorSetInfo memory operatorSetInfo = _operatorSetInfos[operatorSetKey][cert.referenceTimestamp];
        uint256[] memory totalStakes = operatorSetInfo.totalWeights;

        require(totalSignedStakeWeights.length == totalStakeProportionThresholds.length, ArrayLengthMismatch());

        for (uint256 i = 0; i < totalSignedStakeWeights.length; i++) {
            // Calculate threshold as proportion of total signed stake weight
            // totalStakeProportionThresholds is in basis points (e.g. 6600 = 66%)
            uint256 threshold = (totalStakes[i] * totalStakeProportionThresholds[i]) / BPS_DENOMINATOR;

            if (totalSignedStakeWeights[i] < threshold) {
                return false;
            }
        }

        return true;
    }

    ///@inheritdoc IBN254CertificateVerifier
    function verifyCertificateNominal(
        OperatorSet memory operatorSet,
        BN254Certificate memory cert,
        uint256[] memory totalStakeNominalThresholds
    ) external returns (bool) {
        uint256[] memory totalSignedStakeWeights = _verifyCertificate(operatorSet, cert);

        require(totalSignedStakeWeights.length == totalStakeNominalThresholds.length, ArrayLengthMismatch());

        for (uint256 i = 0; i < totalSignedStakeWeights.length; i++) {
            if (totalSignedStakeWeights[i] < totalStakeNominalThresholds[i]) {
                return false;
            }
        }

        return true;
    }

    /**
     *
     *                         INTERNAL FUNCTIONS
     *
     */

    /**
     * @notice Internal function to verify a certificate
     * @param operatorSet The operator set the certificate is for
     * @param cert The certificate to verify
     * @return totalSignedStakeWeights The amount of stake that signed the certificate for each stake type
     */
    function _verifyCertificate(
        OperatorSet memory operatorSet,
        BN254Certificate memory cert
    ) internal returns (uint256[] memory totalSignedStakeWeights) {
        VerificationContext memory ctx;
        ctx.operatorSetKey = operatorSet.key();

        _validateCertificateTimestamp(ctx.operatorSetKey, cert.referenceTimestamp);
        ctx.operatorSetInfo = _operatorSetInfos[ctx.operatorSetKey][cert.referenceTimestamp];

        // Initialize signed stakes with total stake weights
        ctx.totalSignedStakeWeights = new uint256[](ctx.operatorSetInfo.totalWeights.length);
        for (uint256 i = 0; i < ctx.operatorSetInfo.totalWeights.length; i++) {
            ctx.totalSignedStakeWeights[i] = ctx.operatorSetInfo.totalWeights[i];
        }

        ctx.nonSignerApk = _processNonSigners(ctx, cert);

        _verifySignature(ctx, cert);

        return ctx.totalSignedStakeWeights;
    }

    /**
     * @notice Validates certificate timestamp against staleness requirements
     * @param operatorSetKey The operator set key
     * @param referenceTimestamp The reference timestamp to validate
     */
    function _validateCertificateTimestamp(bytes32 operatorSetKey, uint32 referenceTimestamp) internal view {
        // Assert that the certificate is not stale
        uint32 maxStaleness = _maxStalenessPeriods[operatorSetKey];
        require(maxStaleness == 0 || block.timestamp <= referenceTimestamp + maxStaleness, CertificateStale());

        // Assert that the reference timestamp has been set
        require(_referenceTimestampsSet[operatorSetKey][referenceTimestamp], ReferenceTimestampDoesNotExist());

        // Assert that the root that corresponds to the reference timestamp is not disabled
        require(operatorTableUpdater.isRootValidByTimestamp(referenceTimestamp), RootDisabled());
    }

    /**
     * @notice Processes non-signer witnesses and returns aggregate non-signer public key
     * @param ctx The verification context
     * @param cert The certificate being verified
     * @return nonSignerApk The aggregate public key of non-signers
     */
    function _processNonSigners(
        VerificationContext memory ctx,
        BN254Certificate memory cert
    ) internal returns (BN254.G1Point memory nonSignerApk) {
        nonSignerApk = BN254.G1Point(0, 0);
        uint32 previousOperatorIndex = 0;

        for (uint256 i = 0; i < cert.nonSignerWitnesses.length; i++) {
            BN254OperatorInfoWitness memory witness = cert.nonSignerWitnesses[i];

            if (i > 0) {
                // Enforce strictly increasing order of non-signer operator indices
                require(witness.operatorIndex > previousOperatorIndex, NonSignerIndicesNotSorted());
            }

            require(witness.operatorIndex < ctx.operatorSetInfo.numOperators, InvalidOperatorIndex());

            BN254OperatorInfo memory operatorInfo =
                _getOrCacheNonsignerOperatorInfo(ctx.operatorSetKey, cert.referenceTimestamp, witness);

            nonSignerApk = nonSignerApk.plus(operatorInfo.pubkey);

            // Subtract non-signer stakes from total signed stakes
            for (uint256 j = 0; j < operatorInfo.weights.length; j++) {
                if (j < ctx.totalSignedStakeWeights.length) {
                    ctx.totalSignedStakeWeights[j] -= operatorInfo.weights[j];
                }
            }

            previousOperatorIndex = witness.operatorIndex;
        }
    }

    /**
     * @notice Gets operator info from cache or verifies and caches it
     * @param operatorSetKey The operator set key
     * @param referenceTimestamp The reference timestamp
     * @param witness The operator info witness containing proof data
     * @return operatorInfo The verified operator information
     */
    function _getOrCacheNonsignerOperatorInfo(
        bytes32 operatorSetKey,
        uint32 referenceTimestamp,
        BN254OperatorInfoWitness memory witness
    ) internal returns (BN254OperatorInfo memory operatorInfo) {
        BN254OperatorInfo memory cachedInfo = _operatorInfos[operatorSetKey][referenceTimestamp][witness.operatorIndex];

        // Check if operator info is cached using pubkey existence (weights can be 0)
        bool isInfoCached = (cachedInfo.pubkey.X != 0 || cachedInfo.pubkey.Y != 0);

        if (!isInfoCached) {
            bool verified = _verifyOperatorInfoMerkleProof(
                operatorSetKey,
                referenceTimestamp,
                witness.operatorIndex,
                witness.operatorInfo,
                witness.operatorInfoProof
            );

            require(verified, VerificationFailed());

            _operatorInfos[operatorSetKey][referenceTimestamp][witness.operatorIndex] = witness.operatorInfo;
            operatorInfo = witness.operatorInfo;
        } else {
            operatorInfo = cachedInfo;
        }
    }

    /**
     * @notice Verifies the BLS signature
     * @param ctx The verification context
     * @param cert The certificate containing the signature to verify
     */
    function _verifySignature(VerificationContext memory ctx, BN254Certificate memory cert) internal view {
        // Calculate signer aggregate public key by subtracting non-signers from total
        BN254.G1Point memory signerApk = ctx.operatorSetInfo.aggregatePubkey.plus(ctx.nonSignerApk.negate());

        // Compute EIP-712 digest that binds referenceTimestamp and messageHash
        bytes32 signableDigest = calculateCertificateDigest(cert.referenceTimestamp, cert.messageHash);

        (bool pairingSuccessful, bool signatureValid) =
            trySignatureVerification(signableDigest, signerApk, cert.apk, cert.signature);

        require(pairingSuccessful && signatureValid, VerificationFailed());
    }

    /**
     * @notice Verifies a merkle proof for an operator info
     * @param operatorSetKey The operator set key
     * @param referenceTimestamp The reference timestamp
     * @param operatorIndex The index of the operator
     * @param operatorInfo The operator info
     * @param proof The merkle proof as bytes
     * @return verified Whether the proof is valid
     */
    function _verifyOperatorInfoMerkleProof(
        bytes32 operatorSetKey,
        uint32 referenceTimestamp,
        uint32 operatorIndex,
        BN254OperatorInfo memory operatorInfo,
        bytes memory proof
    ) internal view returns (bool verified) {
        bytes32 leaf = calculateOperatorInfoLeaf(operatorInfo);
        bytes32 root = _operatorSetInfos[operatorSetKey][referenceTimestamp].operatorInfoTreeRoot;
        return proof.verifyInclusionKeccak(root, leaf, operatorIndex);
    }

    /**
     *
     *                         VIEW FUNCTIONS
     *
     */

    ///@inheritdoc IBaseCertificateVerifier
    function getOperatorSetOwner(
        OperatorSet memory operatorSet
    ) external view returns (address) {
        bytes32 operatorSetKey = operatorSet.key();
        return _operatorSetOwners[operatorSetKey];
    }

    ///@inheritdoc IBaseCertificateVerifier
    function maxOperatorTableStaleness(
        OperatorSet memory operatorSet
    ) external view returns (uint32) {
        bytes32 operatorSetKey = operatorSet.key();
        return _maxStalenessPeriods[operatorSetKey];
    }

    ///@inheritdoc IBaseCertificateVerifier
    function latestReferenceTimestamp(
        OperatorSet memory operatorSet
    ) external view returns (uint32) {
        bytes32 operatorSetKey = operatorSet.key();
        return _latestReferenceTimestamps[operatorSetKey];
    }

    ///@inheritdoc IBaseCertificateVerifier
    function isReferenceTimestampSet(
        OperatorSet memory operatorSet,
        uint32 referenceTimestamp
    ) external view returns (bool) {
        bytes32 operatorSetKey = operatorSet.key();
        return _referenceTimestampsSet[operatorSetKey][referenceTimestamp];
    }

    ///@inheritdoc IBaseCertificateVerifier
    function getTotalStakeWeights(
        OperatorSet memory operatorSet,
        uint32 referenceTimestamp
    ) external view returns (uint256[] memory) {
        bytes32 operatorSetKey = operatorSet.key();
        return _operatorSetInfos[operatorSetKey][referenceTimestamp].totalWeights;
    }

    /// @inheritdoc IBaseCertificateVerifier
    function getOperatorCount(
        OperatorSet memory operatorSet,
        uint32 referenceTimestamp
    ) external view returns (uint256) {
        bytes32 operatorSetKey = operatorSet.key();
        return _operatorSetInfos[operatorSetKey][referenceTimestamp].numOperators;
    }

    ///@inheritdoc IBN254CertificateVerifier
    function trySignatureVerification(
        bytes32 msgHash,
        BN254.G1Point memory aggPubkey,
        BN254.G2Point memory apkG2,
        BN254.G1Point memory signature
    ) public view returns (bool pairingSuccessful, bool signatureValid) {
        return BN254SignatureVerifier.verifySignature(
            msgHash,
            signature,
            aggPubkey,
            apkG2,
            true, // use gas limit
            PAIRING_EQUALITY_CHECK_GAS
        );
    }

    ///@inheritdoc IBN254CertificateVerifier
    function getNonsignerOperatorInfo(
        OperatorSet memory operatorSet,
        uint32 referenceTimestamp,
        uint256 operatorIndex
    ) external view returns (BN254OperatorInfo memory) {
        bytes32 operatorSetKey = operatorSet.key();
        return _operatorInfos[operatorSetKey][referenceTimestamp][operatorIndex];
    }

    ///@inheritdoc IBN254CertificateVerifier
    function isNonsignerCached(
        OperatorSet memory operatorSet,
        uint32 referenceTimestamp,
        uint256 operatorIndex
    ) external view returns (bool) {
        bytes32 operatorSetKey = operatorSet.key();
        BN254OperatorInfo memory operatorInfo = _operatorInfos[operatorSetKey][referenceTimestamp][operatorIndex];
        // Check if operator info is cached using pubkey existence (weights can be 0)
        return operatorInfo.pubkey.X != 0 && operatorInfo.pubkey.Y != 0;
    }

    ///@inheritdoc IBN254CertificateVerifier
    function getOperatorSetInfo(
        OperatorSet memory operatorSet,
        uint32 referenceTimestamp
    ) external view returns (BN254OperatorSetInfo memory) {
        bytes32 operatorSetKey = operatorSet.key();
        return _operatorSetInfos[operatorSetKey][referenceTimestamp];
    }

    /// @inheritdoc IBN254CertificateVerifier
    function calculateCertificateDigest(uint32 referenceTimestamp, bytes32 messageHash) public pure returns (bytes32) {
        return keccak256(abi.encode(BN254_CERTIFICATE_TYPEHASH, referenceTimestamp, messageHash));
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable-v4.9.0/contracts/proxy/utils/Initializable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.2;

import "../../utils/AddressUpgradeable.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     * @custom:oz-retyped-from bool
     */
    uint8 private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint8 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
     * constructor.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: setting the version to 255 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint8 version) {
        require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
        _initialized = version;
        _initializing = true;
        _;
        _initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized != type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint8) {
        return _initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _initializing;
    }
}
"
    },
    "src/contracts/libraries/BN254.sol": {
      "content": "// SPDX-License-Identifier: MIT
// several functions are taken or adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol (MIT license):
// Copyright 2017 Christian Reitwiessner
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// The remainder of the code in this library is written by LayrLabs Inc. and is also under an MIT license

pragma solidity ^0.8.27;

/**
 * @title Library for operations on the BN254 elliptic curve.
 * @author Layr Labs, Inc.
 * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
 * @notice Contains BN254 parameters, common operations (addition, scalar mul, pairing), and BLS signature functionality.
 */
library BN254 {
    // modulus for the underlying field F_p of the elliptic curve
    uint256 internal constant FP_MODULUS =
        21_888_242_871_839_275_222_246_405_745_257_275_088_696_311_157_297_823_662_689_037_894_645_226_208_583;
    // modulus for the underlying field F_r of the elliptic curve
    uint256 internal constant FR_MODULUS =
        21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_617;

    struct G1Point {
        uint256 X;
        uint256 Y;
    }

    // Encoding of field elements is: X[1] * i + X[0]
    struct G2Point {
        uint256[2] X;
        uint256[2] Y;
    }

    /// @dev Thrown when the sum of two points of G1 fails
    error ECAddFailed();
    /// @dev Thrown when the scalar multiplication of a point of G1 fails
    error ECMulFailed();
    /// @dev Thrown when the scalar is too large.
    error ScalarTooLarge();
    /// @dev Thrown when the pairing check fails
    error ECPairingFailed();
    /// @dev Thrown when the exponentiation mod fails
    error ExpModFailed();

    function generatorG1() internal pure returns (G1Point memory) {
        return G1Point(1, 2);
    }

    // generator of group G2
    /// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + it1).
    uint256 internal constant G2x1 =
        11_559_732_032_986_387_107_991_004_021_392_285_783_925_812_861_821_192_530_917_403_151_452_391_805_634;
    uint256 internal constant G2x0 =
        10_857_046_999_023_057_135_944_570_762_232_829_481_370_756_359_578_518_086_990_519_993_285_655_852_781;
    uint256 internal constant G2y1 =
        4_082_367_875_863_433_681_332_203_403_145_435_568_316_851_327_593_401_208_105_741_076_214_120_093_531;
    uint256 internal constant G2y0 =
        8_495_653_923_123_431_417_604_973_247_489_272_438_418_190_587_263_600_148_770_280_649_306_958_101_930;

    /// @notice returns the G2 generator
    /// @dev mind the ordering of the 1s and 0s!
    ///      this is because of the (unknown to us) convention used in the bn254 pairing precompile contract
    ///      "Elements a * i + b of F_p^2 are encoded as two elements of F_p, (a, b)."
    ///      https://github.com/ethereum/EIPs/blob/master/EIPS/eip-197.md#encoding
    function generatorG2() internal pure returns (G2Point memory) {
        return G2Point([G2x1, G2x0], [G2y1, G2y0]);
    }

    // negation of the generator of group G2
    /// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + it1).
    uint256 internal constant nG2x1 =
        11_559_732_032_986_387_107_991_004_021_392_285_783_925_812_861_821_192_530_917_403_151_452_391_805_634;
    uint256 internal constant nG2x0 =
        10_857_046_999_023_057_135_944_570_762_232_829_481_370_756_359_578_518_086_990_519_993_285_655_852_781;
    uint256 internal constant nG2y1 =
        17_805_874_995_975_841_540_914_202_342_111_839_520_379_459_829_704_422_454_583_296_818_431_106_115_052;
    uint256 internal constant nG2y0 =
        13_392_588_948_715_843_804_641_432_497_768_002_650_278_120_570_034_223_513_918_757_245_338_268_106_653;

    function negGeneratorG2() internal pure returns (G2Point memory) {
        return G2Point([nG2x1, nG2x0], [nG2y1, nG2y0]);
    }

    bytes32 internal constant powersOfTauMerkleRoot = 0x22c998e49752bbb1918ba87d6d59dd0e83620a311ba91dd4b2cc84990b31b56f;

    /**
     * @param p Some point in G1.
     * @return The negation of `p`, i.e. p.plus(p.negate()) should be zero.
     */
    function negate(
        G1Point memory p
    ) internal pure returns (G1Point memory) {
        // The prime q in the base field F_q for G1
        if (p.X == 0 && p.Y == 0) {
            return G1Point(0, 0);
        } else {
            return G1Point(p.X, FP_MODULUS - (p.Y % FP_MODULUS));
        }
    }

    /**
     * @return r the sum of two points of G1
     */
    function plus(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) {
        uint256[4] memory input;
        input[0] = p1.X;
        input[1] = p1.Y;
        input[2] = p2.X;
        input[3] = p2.Y;
        bool success;

        // solium-disable-next-line security/no-inline-assembly
        assembly {
            success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40)
            // Use "invalid" to make gas estimation work
            switch success
            case 0 { invalid() }
        }

        require(success, ECAddFailed());
    }

    /**
     * @notice an optimized ecMul implementation that takes O(log_2(s)) ecAdds
     * @param p the point to multiply
     * @param s the scalar to multiply by
     * @dev this function is only safe to use if the scalar is 9 bits or less
     */
    function scalar_mul_tiny(BN254.G1Point memory p, uint16 s) internal view returns (BN254.G1Point memory) {
        require(s < 2 ** 9, ScalarTooLarge());

        // if s is 1 return p
        if (s == 1) {
            return p;
        }

        // the accumulated product to return
        BN254.G1Point memory acc = BN254.G1Point(0, 0);
        // the 2^n*p to add to the accumulated product in each iteration
        BN254.G1Point memory p2n = p;
        // value of most significant bit
        uint16 m = 1;
        // index of most significant bit
        uint8 i = 0;

        //loop until we reach the most significant bit
        while (s >= m) {
            unchecked {
                // if the  current bit is 1, add the 2^n*p to the accumulated product
                if ((s >> i) & 1 == 1) {
                    acc = plus(acc, p2n);
                }
                // double the 2^n*p for the next iteration
                p2n = plus(p2n, p2n);

                // increment the index and double the value of the most significant bit
                m <<= 1;
                ++i;
            }
        }

        // return the accumulated product
        return acc;
    }

    /**
     * @return r the product of a point on G1 and a scalar, i.e.
     *         p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all
     *         points p.
     */
    function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
        uint256[3] memory input;
        input[0] = p.X;
        input[1] = p.Y;
        input[2] = s;
        bool success;
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40)
            // Use "invalid" to make gas estimation work
            switch success
            case 0 { invalid() }
        }
        require(success, ECMulFailed());
    }

    /**
     *  @return The result of computing the pairing check
     *         e(p1[0], p2[0]) *  .... * e(p1[n], p2[n]) == 1
     *         For example,
     *         pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
     */
    function pairing(
        G1Point memory a1,
        G2Point memory a2,
        G1Point memory b1,
        G2Point memory b2
    ) internal view returns (bool) {
        G1Point[2] memory p1 = [a1, b1];
        G2Point[2] memory p2 = [a2, b2];

        uint256[12] memory input;

        for (uint256 i = 0; i < 2; i++) {
            uint256 j = i * 6;
            input[j + 0] = p1[i].X;
            input[j + 1] = p1[i].Y;
            input[j + 2] = p2[i].X[0];
            input[j + 3] = p2[i].X[1];
            input[j + 4] = p2[i].Y[0];
            input[j + 5] = p2[i].Y[1];
        }

        uint256[1] memory out;
        bool success;

        // solium-disable-next-line security/no-inline-assembly
        assembly {
            success := staticcall(sub(gas(), 2000), 8, input, mul(12, 0x20), out, 0x20)
            // Use "invalid" to make gas estimation work
            switch success
            case 0 { invalid() }
        }

        require(success, ECPairingFailed());

        return out[0] != 0;
    }

    /**
     * @notice This function is functionally the same as pairing(), however it specifies a gas limit
     *         the user can set, as a precompile may use the entire gas budget if it reverts.
     */
    function safePairing(
        G1Point memory a1,
        G2Point memory a2,
        G1Point memory b1,
        G2Point memory b2,
        uint256 pairingGas
    ) internal view returns (bool, bool) {
        G1Point[2] memory p1 = [a1, b1];
        G2Point[2] memory p2 = [a2, b2];

        uint256[12] memory input;

        for (uint256 i = 0; i < 2; i++) {
            uint256 j = i * 6;
            input[j + 0] = p1[i].X;
            input[j + 1] = p1[i].Y;
            input[j + 2] = p2[i].X[0];
            input[j + 3] = p2[i].X[1];
            input[j + 4] = p2[i].Y[0];
            input[j + 5] = p2[i].Y[1];
        }

        uint256[1] memory out;
        bool success;

        // solium-disable-next-line security/no-inline-assembly
        assembly {
            success := staticcall(pairingGas, 8, input, mul(12, 0x20), out, 0x20)
        }

        //Out is the output of the pairing precompile, either 0 or 1 based on whether the two pairings are equal.
        //Success is true if the precompile actually goes through (aka all inputs are valid)

        return (success, out[0] != 0);
    }

    /// @return hashedG1 the keccak256 hash of the G1 Point
    /// @dev used for BLS signatures
    function hashG1Point(
        BN254.G1Point memory pk
    ) internal pure returns (bytes32 hashedG1) {
        assembly {
            mstore(0, mload(pk))
            mstore(0x20, mload(add(0x20, pk)))
            hashedG1 := keccak256(0, 0x40)
        }
    }

    /// @return the keccak256 hash of the G2 Point
    /// @dev used for BLS signatures
    function hashG2Point(
        BN254.G2Point memory pk
    ) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(pk.X[0], pk.X[1], pk.Y[0], pk.Y[1]));
    }

    /**
     * @notice adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol
     */
    function hashToG1(
        bytes32 _x
    ) internal view returns (G1Point memory) {
        uint256 beta = 0;
        uint256 y = 0;

        uint256 x = uint256(_x) % FP_MODULUS;

        while (true) {
            (beta, y) = findYFromX(x);

            // y^2 == beta
            if (beta == mulmod(y, y, FP_MODULUS)) {
                return G1Point(x, y);
            }

            x = addmod(x, 1, FP_MODULUS);
        }
        return G1Point(0, 0);
    }

    /**
     * Given X, find Y
     *
     *   where y = sqrt(x^3 + b)
     *
     * Returns: (x^3 + b), y
     */
    function findYFromX(
        uint256 x
    ) internal view returns (uint256, uint256) {
        // beta = (x^3 + b) % p
        uint256 beta = addmod(mulmod(mulmod(x, x, FP_MODULUS), x, FP_MODULUS), 3, FP_MODULUS);

        // y^2 = x^3 + b
        // this acts like: y = sqrt(beta) = beta^((p+1) / 4)
        uint256 y = expMod(beta, 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52, FP_MODULUS);

        return (beta, y);
    }

    function expMod(uint256 _base, uint256 _exponent, uint256 _modulus) internal view returns (uint256 retval) {
        bool success;
        uint256[1] memory output;
        uint256[6] memory input;
        input[0] = 0x20; // baseLen = new(big.Int).SetBytes(getData(input, 0, 32))
        input[1] = 0x20; // expLen  = new(big.Int).SetBytes(getData(input, 32, 32))
        input[2] = 0x20; // modLen  = new(big.Int).SetBytes(getData(input, 64, 32))
        input[3] = _base;
        input[4] = _exponent;
        input[5] = _modulus;
        assembly {
            success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20)
            // Use "invalid" to make gas estimation work
            switch success
            case 0 { invalid() }
        }
        require(success, ExpModFailed());
        return output[0];
    }
}
"
    },
    "src/contracts/libraries/BN254SignatureVerifier.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import {BN254} from "./BN254.sol";

/**
 * @title BN254SignatureVerifier
 * @notice Library for BN254 signature verification
 * @dev Provides unified signature verification with consistent gamma calculation and hash-to-G1 conversion
 */
library BN254SignatureVerifier {
    using BN254 for BN254.G1Point;

    /**
     * @notice Core BN254 signature verification function with optional gas limiting
     * @param msgHash The message hash that was signed
     * @param signature The BLS signature to verify (G1 point)
     * @param pubkeyG1 The G1 component of the public key
     * @param pubkeyG2 The G2 component of the public key
     * @param useGasLimit Whether to use gas-limited safe pairing
     * @param pairingGas Gas limit for pairing (ignored if useGasLimit is false)
     * @return success True if verification succeeded (always true if useGasLimit=false due to revert)
     * @return pairingSuccessful True if pairing operation completed (only relevant when useGasLimit=true)
     */
    function verifySignature(
        bytes32 msgHash,
        BN254.G1Point memory signature,
        BN254.G1Point memory pubkeyG1,
        BN254.G2Point memory pubkeyG2,
        bool useGasLimit,
        uint256 pairingGas
    ) internal view returns (bool success, bool pairingSuccessful) {
        BN254.G1Point memory messagePoint = BN254.hashToG1(msgHash);
        uint256 gamma = _calculateGamma(msgHash, pubkeyG1, pubkeyG2, signature);

        // Calculate pairing inputs
        BN254.G1Point memory leftG1 = signature.plus(pubkeyG1.scalar_mul(gamma));
        BN254.G1Point memory rightG1 = messagePoint.plus(BN254.generatorG1().scalar_mul(gamma));

        if (useGasLimit) {
            // Use safe pairing with gas limit
            (pairingSuccessful, success) =
                BN254.safePairing(leftG1, BN254.negGeneratorG2(), rightG1, pubkeyG2, pairingGas);
        } else {
            success = BN254.pairing(leftG1, BN254.negGeneratorG2(), rightG1, pubkeyG2);
            if (success) {
                pairingSuccessful = true;
            }
        }
    }

    /**
     * @notice Internal function to calculate gamma value for signature verification
     * @param msgHash The message hash
     * @param pubkeyG1 The G1 component of the public key
     * @param pubkeyG2 The G2 component of the public key
     * @param signature The signature point
     * @return gamma The calculated gamma value
     */
    function _calculateGamma(
        bytes32 msgHash,
        BN254.G1Point memory pubkeyG1,
        BN254.G2Point memory pubkeyG2,
        BN254.G1Point memory signature
    ) internal pure returns (uint256 gamma) {
        gamma = uint256(
            keccak256(
                abi.encodePacked(
                    msgHash,
                    pubkeyG1.X,
                    pubkeyG1.Y,
                    pubkeyG2.X[0],
                    pubkeyG2.X[1],
                    pubkeyG2.Y[0],
                    pubkeyG2.Y[1],
                    signature.X,
                    signature.Y
                )
            )
        ) % BN254.FR_MODULUS;
    }
}
"
    },
    "src/contracts/libraries/Merkle.sol": {
      "content": "// SPDX-License-Identifier: MIT
// Adapted from OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/MerkleProof.sol)

pragma solidity ^0.8.0;

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, salt the leaves, or hash the leaves with a hash function other than
 * what is used for the Merkle tree's internal nodes. This is because the
 * concatenation of a sorted pair of internal nodes in the Merkle tree could
 * be reinterpreted as a leaf value.
 */
library Merkle {
    /// @notice Thrown when the provided proof was not a multiple of 32, or was empty for SHA256.
    /// @dev Error code: 0x4dc5f6a4
    error InvalidProofLength();

    /// @notice Thrown when the provided index was outside the max index for the tree.
    /// @dev Error code: 0x63df8171
    error InvalidIndex();

    /// @notice Thrown when the provided leaves' length was not a power of two.
    /// @dev Error code: 0xf6558f51
    error LeavesNotPowerOfTwo();

    /// @notice Thrown when the provided leaves' length was 0.
    /// @dev Error code: 0xbaec3d9a
    error NoLeaves();

    /// @notice Thrown when the provided leaves' length was insufficient.
    /// @dev Error code: 0xf8ef0367
    /// @dev This is used for the SHA256 Merkle tree, where the tree must have more than 1 leaf.
    error NotEnoughLeaves();

    /// @notice Thrown when the root is empty.
    /// @dev Error code: 0x53ce4ece
    /// @dev Empty roots should never be valid. We prevent them to avoid issues like the Nomad bridge attack: <https://medium.com/nomad-xyz-blog/nomad-bridge-hack-root-cause-analysis-875ad2e5aacd>
    error EmptyRoot();

    /**
     * @notice Verifies that a given leaf is included in a Merkle tree
     * @param proof The proof of inclusion for the leaf
     * @param root The root of the Merkle tree
     * @param leaf The leaf to verify
     * @param index The index of the leaf in the Merkle tree
     * @return True if the leaf is included in the Merkle tree, false otherwise
     * @dev A `proof` is valid if and only if the rebuilt hash matches the root of the tree.
     * @dev Reverts for:
     *      - InvalidProofLength: proof.length is not a multiple of 32.
     *      - InvalidIndex: index is not 0 at conclusion of computation (implying outside the max index for the tree).
     */
    function verifyInclusionKeccak(
        bytes memory proof,
        bytes32 root,
        bytes32 leaf,
        uint256 index
    ) internal pure returns (bool) {
        require(root != bytes32(0), EmptyRoot());
        return processInclusionProofKeccak(proof, leaf, index) == root;
    }

    /**
     * @notice Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`.
     * @param proof The proof of inclusion for the leaf
     * @param leaf The leaf to verify
     * @param index The index of the leaf in the Merkle tree
     * @return The rebuilt hash
     * @dev Reverts for:
     *      - InvalidProofLength: proof.length is not a multiple of 32.
     *      - InvalidIndex: index is not 0 at conclusion of computation (implying outside the max index for the tree).
     * @dev The tree is built assuming `leaf` is the 0 indexed `index`'th leaf from the bottom left of the tree.
     */
    function processInclusionProofKeccak(
        bytes memory proof,
        bytes32 leaf,
        uint256 index
    ) internal pure returns (bytes32) {
        if (proof.length == 0) {
            return leaf;
        }

        require(proof.length % 32 == 0, InvalidProofLength());

        bytes32 computedHash = leaf;
        for (uint256 i = 32; i <= proof.length; i += 32) {
            if (index % 2 == 0) {
                // if index is even, then computedHash is a left sibling
                assembly {
                    mstore(0x00, computedHash)
                    mstore(0x20, mload(add(proof, i)))
                    computedHash := keccak256(0x00, 0x40)
                    index := div(index, 2)
                }
            } else {
                // if index is odd, then computedHash is a right sibling
                assembly {
                    mstore(0x00, mload(add(proof, i)))
                    mstore(0x20, computedHash)
                    computedHash := keccak256(0x00, 0x40)
                    index := div(index, 2)
                }
            }
        }

        // Confirm proof was fully consumed by end of computation
        require(index == 0, InvalidIndex());

        return computedHash;
    }

    /**
     * @notice Verifies that a given leaf is included in a Merkle tree
     * @param proof The proof of inclusion for the leaf
     * @param root The root of the Merkle tree
     * @param leaf The leaf to verify
     * @param index The index of the leaf in the Merkle tree
     * @return True if the leaf is included in the Merkle tree, false otherwise
     * @dev A `proof` is valid if and only if the rebuilt hash matches the root of the tree.
     * @dev Reverts for:
     *      - InvalidProofLength: proof.length is 0 or not a multiple of 32.
     *      - InvalidIndex: index is not 0 at conclusion of computation (implying outside the max index for the tree).
     */
    function verifyInclusionSha256(
        bytes memory proof,
        bytes32 root,
        bytes32 leaf,
        uint256 index
    ) internal view returns (bool) {
        require(root != bytes32(0), EmptyRoot());
        return processInclusionProofSha256(proof, leaf, index) == root;
    }

    /**
     * @notice Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`.
     * @param proof The proof of inclusion for the leaf
     * @param leaf The leaf to verify
     * @param index The index of the leaf in the Merkle tree
     * @return The rebuilt hash
     * @dev Reverts for:
     *      - InvalidProofLength: proof.length is 0 or not a multiple of 32.
     *      - InvalidIndex: index is not 0 at conclusion of computation (implying outside the max index for the tree).
     * @dev The tree is built assuming `leaf` is the 0 indexed `index`'th leaf from the bottom left of the tree.
     */
    function processInclusionProofSha256(
        bytes memory proof,
        bytes32 leaf,
        uint256 index
    ) internal view returns (bytes32) {
        require(proof.length != 0 && proof.length % 32 == 0, InvalidProofLength());
        bytes32[1] memory computedHash = [leaf];
        for (uint256 i = 32; i <= proof.length; i += 32) {
            if (index % 2 == 0) {
                // if index is even, then computedHash is a left sibling
                assembly {
                    mstore(0x00, mload(computedHash))
                    mstore(0x20, mload(add(proof, i)))
                    if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) { revert(0, 0) }
                    index := div(index, 2)
                }
            } else {
                // if index is odd, then computedHash is a right sibling
                assembly {
                    mstore(0x00, mload(add(proof, i)))
                    mstore(0x20, mload(computedHash))
                    if iszero(staticcall(sub(gas(), 2000), 2, 0x00, 0x40, computedHash, 0x20)) { revert(0, 0) }
                    index := div(index, 2)
                }
            }
        }

        // Confirm proof was fully consumed by end of computation
        require(index == 0, InvalidIndex());

        return computedHash[0];
    }

    /**
     * @notice Returns the Merkle root of a tree created from a set of leaves using SHA-256 as its hash function
     * @param leaves the leaves of the Merkle tree
     * @return The computed Merkle root of the tree.
     * @dev Reverts for:
     *      - NotEnoughLeaves: leaves.length is less than 2.
     *      - LeavesNotPowerOfTwo: leaves.length is not a power of two.
     * @dev Unlike the Keccak version, this function does not allow a single-leaf tree.
     */
    function merkleizeSha256(
        bytes32[] memory leaves
    ) internal pure returns (bytes32) {
        require(leaves.length > 1, NotEnoughLeaves());
        require(isPowerOfTwo(leaves.length), LeavesNotPowerOfTwo());

        // There are half as many nodes in the layer above the leaves
        uint256 numNodesInLayer = leaves.length / 2;
        // Create a layer to store the internal nodes
        bytes32[] memory layer = new bytes32[](numNodesInLayer);
        // Fill the layer with the pairwise hashes of the leaves
        for (uint256 i = 0; i < numNodesInLayer; i++) {
            layer[i] = sha256(abi.encodePacked(leaves[2 * i], leaves[2 * i + 1]));
        }

        // While we haven't computed the root
        while (numNodesInLayer != 1) {
            // The next layer above has half as many nodes
            numNodesInLayer /= 2;
            // Overwrite the first numNodesInLayer nodes in layer with the pairwise hashes of their children
            for (uint256 i = 0; i < numNodesInLayer; i++) {
                layer[i] = sha256(abi.encodePacked(layer[2 * i], layer[2 * i + 1]));
            }
        }
        // The first node in the layer is the root
        return layer[0];
    }

    /**
     * @notice Returns the Merkle root of a tree created from a set of leaves using Keccak as its hash function
     * @param leaves the leaves of the Merkle tree
     * @return The computed Merkle root of the tree.
     * @dev Reverts for:
     *      - NoLeaves: leaves.length is 0.
     */
    function merkleizeKeccak(
        bytes32[] memory leaves
    ) internal pure returns (bytes32) {
        require(leaves.length > 0, NoLeaves());

        uint256 numNodesInLayer;
        if (!isPowerOfTwo(leaves.length)) {
            // Pad to the next power of 2
            numNodesInLayer = 1;
            while (numNodesInLayer < leaves.length) {
                numNodesInLayer *= 2;
            }
        } else {
            numNodesInLayer = leaves.length;
        }

        // Create a layer to store the internal nodes
        bytes32[] memory layer = new bytes32[](numNodesInLayer);
        for (uint256 i = 0; i < leaves.length; i++) {
            layer[i] = leaves[i];
        }

        // While we haven't computed the root
        while (numNodesInLayer != 1) {
            // The next layer above has half as many nodes
            numNodesInLayer /= 2;
            // Overwrite the first numNodesInLayer nodes in layer with the pairwise hashes of their children
            for (uint256 i = 0; i < numNodesInLayer; i++) {
                layer[i] = keccak256(abi.encodePacked(layer[2 * i], layer[2 * i + 1]));
            }
        }
        // The first node in the layer is the root
        return layer[0];
    }

    /**
     * @notice Returns the Merkle proof for a given index in a tree created from a set of leaves using Keccak as its hash function
     * @param leaves the leaves of the Merkle tree
     * @param index the index of the leaf to get the proof for
     * @return proof The computed Merkle proof for the leaf at index.
     * @dev Reverts for:
     *      - InvalidIndex: index is outside the max index for the tree.
     */
    function getProofKeccak(bytes32[] memory leaves, uint256 index) internal pure returns (bytes memory proof) {
        require(leaves.length > 0, NoLeaves());
        // TODO: very inefficient, use ZERO_HASHES
        // pad to the next power of 2
        uint256 numNodesInLayer = 1;
        while (numNodesInLayer < leaves.length) {
            numNodesInLayer *= 2;
        }
        bytes32[] memory layer = new bytes32[](numNodesInLayer);
        for (uint256 i = 0; i < leaves.length; i++) {
            layer[i] = leaves[i];
        }

        if (index >= layer.length) revert InvalidIndex();

        // While we haven't computed the root
        while (numNodesInLayer != 1) {
            // Flip the least significant bit of index to get the sibling index
            uint256 siblingIndex = index ^ 1;
            // Add the sibling to the proof
            proof = abi.encodePacked(proof, layer[siblingIndex]);
            index /= 2;

            // The next layer above has half as many nodes
            numNodesInLayer /= 2;
            // Overwrite the first numNodesInLayer nodes in layer with the pairwise hashes of their children
            for (uint256 i = 0; i < numNodesInLayer; i++) {
                layer[i] = keccak256(abi.encodePacked(layer[2 * i], layer[2 * i + 1]));
            }
        }
    }

    /**
     * @notice Returns the Merkle proof for a given index in a tree created from a set of leaves using SHA-256 as its hash function
     * @param leaves the leaves of the Merkle tree
     * @param index the index of the leaf to get the proof for
     * @return proof The computed Merkle proof for the leaf at index.
     * @dev Reverts for:
     *      - NotEnoughLeaves: leaves.length is less than 2.
     * @dev Unlike the Keccak version, this function does not allow a single-leaf proof.
     */
    function getProofSha256(bytes32[] memory leaves, uint256 index) internal pure returns (bytes memory proof) {
        require(leaves.length > 1, NotEnoughLeaves());
        // TODO: very inefficient, use ZERO_HASHES
        // pad to the next power of 2
        uint256 numNodesInLayer = 1;
        while (numNodesInLayer < leaves.length) {
            numNodesInLayer *= 2;
        }
        bytes32[] memory layer = new bytes32[](numNodesInLayer);
        for (uint256 i = 0; i < leaves.length; i++) {
            layer[i] = leaves[i];
        }

        if (index >= layer.length) revert InvalidIndex();

        // While we haven't computed the root
        while (numNodesInLayer != 1) {
            // Flip the least significant bit of index to get the sibling index
            uint256 siblingIndex = index ^ 1;
            // Add the sibling to the proof
            proof = abi.encodePacked(proof, layer[siblingIndex]);
            index /= 2;

            // The next layer above has half as many nodes
            numNodesInLayer /= 2;
            // Overwrite the first numNodesInLayer nodes in layer with the pairwise hashes of their children
            for (uint256 i = 0; i < numNodesInLayer; i++) {
                layer[i] = sha256(abi.encodePacked(layer[2 * i], layer[2 * i + 1]));
            }
        }
    }

    /**
     * @notice Returns whether the input is a power of two
     * @param value the value to check
     * @return True if the input is a power of two, false otherwise
     */
    function isPowerOfTwo(
        uint256 value
    ) internal pure returns (bool) {
        return value != 0 && (value & (value - 1)) == 0;
    }
}
"
    },
    "src/contracts/libraries/OperatorSetLib.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

using OperatorSetLib for OperatorSet global;

/**
 * @notice An operator set identified by the AVS address and an identifier
 * @param avs The address of the AVS this operator set belongs to
 * @param id The unique identifier for the operator set
 */
struct OperatorSet {
    address avs;
    uint32 id;
}

library OperatorSetLib {
    function key(
        OperatorSet memory os
    ) internal pure returns (bytes32) {
        return bytes32(abi.encodePacked(os.avs, uint96(os.id)));
    }

    function decode(
        bytes32 _key
    ) internal pure returns (OperatorSet memory) {
        /// forgefmt: disable-next-item
        return OperatorSet({
            avs: address(uint160(uint256(_key) >> 96)),
            id: uint32(uint256(_key) & type(uint96).max)
        });
    }
}
"
    },
    "src/contracts/mixins/SemVerMixin.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import "../interfaces/ISemVerMixin.sol";
import "@openzeppelin-upgrades/contracts/utils/ShortStringsUpgradeable.sol";

/// @title SemVerMixin
/// @notice A mixin contract that provides semantic versioning functionality.
/// @dev Follows SemVer 2.0.0 specification (https://semver.org/).
abstract contract SemVerMixin is ISemVerMixin {
    using ShortStringsUpgradeable for *;

    /// @notice The semantic version string for this contract, stored as a ShortString for gas efficiency.
    /// @dev Follows SemVer 2.0.0 specification (https://semver.org/).
    ShortString internal immutable _VERSION;

    /// @notice Initializes the contract with a semantic version string.
    /// @param _version The SemVer-formatted version string (e.g., "1.2.3")
    /// @dev Version should follow SemVer 2.0.0 format: MAJOR.MINOR.PATCH
    constructor(
        string memory _version
    ) {
        _VERSION = _version.toShortString();
    }

    /// @inheritdoc ISemVerMixin
    function version() public view virtual returns (string memory) {
        return _VERSION.toString();
    }

    /// @notice Returns the major version of the contract.
    /// @dev Supports single digit major versions (e.g., "1" for version "1.2.3")
    /// @return The major version string (e.g., "1" for version "1.2.3")
    function _majorVersion() internal view returns (string memory) {
        bytes memory v = bytes(_VERSION.toString());
        return string(abi.encodePacked(v[0]));
    }
}
"
    },
    "src/contracts/mixins/LeafCalculatorMixin.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import {IOperatorTableCalculatorTypes} from "../interfaces/IOperatorTableCalculator.sol";

/**
 * @title LeafCalculatorMixin
 * @notice Reusable mixin for calculating operator info and operator table leaf hashes
 * @dev Provides standardized leaf calculation functions for use across multiple contracts and repositories.
 *      This mixin centralizes the leaf hashing logic to ensure consistency across the EigenLayer ecosystem
 *      and maintains proper cryptographic security through salt-based domain separation.
 */
abstract contract LeafCalculatorMixin {
    /// @dev Salt for operator info leaf hash calculation
    /// @dev The salt is used to prevent against second preimage attacks: attacks where an
    /// attacker can create a partial proof using an internal node rather than a leaf to
    /// validate a proof. The salt ensures that leaves cannot be concatenated together to
    /// form a valid proof, as well as reducing the likelihood of an internal node matching
    /// the salt prefix.
    /// @dev Value derived from keccak256("OPERATOR_INFO_LEAF_SALT") = 0x75...
    /// This ensures collision resistance and semantic meaning.
    uint8 public constant OPERATOR_INFO_LEAF_SALT = 0x75;

    /// @dev Salt for operator table leaf hash calculation
    /// @dev The salt is used to prevent against second preimage attacks: attacks where an
    /// attacker can create a partial proof using an internal node rather than a leaf to
    /// validate a proof. The salt ensures that leaves cannot be concatenated together to
    /// form a valid proof, as well as reducing the likelihood of an internal node matching
    /// the salt prefix.
    /// @dev Value derived from keccak256("OPERATOR_TABLE_LEAF_SALT") = 0x8e...
    /// This ensures collision resistance and semantic meaning.
    uint8 public constant OPERATOR_TABLE_LEAF_SALT = 0x8e;

    /**
     * @notice Calculate the leaf hash for an operator info
     * @param operatorInfo The BN254 operator info struct containing the operator's public key and stake weights
     * @return The leaf hash (keccak256 of salt and encoded operator info)
     * @dev The salt is used to prevent against second preimage attacks: attacks where an
     * attacker can create a partial proof using an internal node rather than a leaf to
     * validate a proof. The salt ensures that leaves cannot be concatenated together to
     * form a valid proof, as well as reducing the likelihood of an internal node matching
     * the salt prefix.
     *
     * This is a standard "domain separation" technique in Merkle tree implementations
     * to ensure leaf nodes and internal nodes can never be confused with each other.
     * See Section 2.1 of <https://www.rfc-editor.org/rfc/rfc9162#name-merkle-trees> for more.
     *
     * Uses abi.encodePacked for the salt and abi.encode for the struct to handle complex types
     * (structs with dynamic arrays) while maintaining gas efficiency where possible.
     */
    function calculateOperatorInfoLeaf(
        IOperatorTableCalculatorTypes.BN254OperatorInfo memory operatorInfo
    ) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(OPERATOR_INFO_LEAF_SALT, abi.encode(operatorInfo)));
    }

    /**
     * @notice Calculate the leaf hash for an operator table
     * @param operatorTableBytes The encoded operator table as bytes containing operator set data
     * @return The leaf hash (keccak256 of salt and operator table bytes)
     * @dev The salt is used to prevent against second preimage attacks: attacks where an
     * attacker can create a partial proof using an internal node rather than a leaf to
     * validate a proof. The salt ensures that leaves cannot be concatenated together to
     * form a valid proof, as well as reducing the likelihood of an internal node matching
     * the salt prefix.
     *
     * This is a standard "domain separation" technique in Merkle tree implementations
     * to ensure leaf nodes and internal nodes can never be confused with each other.
     * See Section 2.1 of <https://www.rfc-editor.org/rfc/rfc9162#name-merkle-trees> for more.
     *
     * Uses abi.encodePacked for both salt and bytes for optimal gas efficiency since both
     * are simple byte arrays without complex nested structures.
     */
    function calculateOperatorTableLeaf(
        bytes calldata operatorTableBytes
    ) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(OPERATOR_TABLE_LEAF_SALT, operatorTableBytes));
    }
}
"
    },
    "src/contracts/multichain/BN254CertificateVerifierStorage.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import "../interfaces/IOperatorTableUpdater.sol";
import "../interfaces/IBN254CertificateVerifier.sol";
import "../interfaces/IBaseCertificateVerifier.sol";

abstract contract BN254CertificateVerifierStorage is IBN254CertificateVerifier {
    // Constants

    /// @dev EIP-712 type hash for certificate verification
    bytes32 internal constant BN254_CERTIFICATE_TYPEHASH =
        keccak256("BN254Certificate(uint32 referenceTimestamp,bytes32 messageHash)");

    /// @dev Gas limit for pairing operations to prevent DoS attacks
    uint256 internal constant PAIRING_EQUALITY_CHECK_GAS = 400_000;

    /// @dev Basis point unit denominator for division
    uint256 internal constant BPS_DENOMINATOR = 10_000;

    // OPERATOR_INFO_LEAF_SALT is now inherited from LeafCalculatorMixin

    // Immutables

    /// @dev The address that can update operator tables
    IOperatorTableUpdater public immutable operatorTableUpdater;

    // Mutatables

    /// @dev Mapping from operatorSet key to owner address
    mapping(bytes32 => address) internal _operatorSetOwners;

    /// @dev Mapping from operatorSet key to maximum staleness period
    mapping(bytes32 => uint32) internal _maxStalenessPeriods;

    /// @dev Mapping from operatorSet key to latest reference timestamp
    mapping(bytes32 => uint32) internal _latestReferenceTimestamps;

    /// @dev Mapping from operatorSet key to reference timestamp to operator set info
    mapping(bytes32 => mapping(uint32 => BN254OperatorSetInfo)) internal _operatorSetInfos;

    /// @dev Mapping from operatorSet key to reference timestamp to operator index to operator info
    /// This is used to cache operator info that has been proven against a tree root

Tags:
Multisig, Voting, Upgradeable, Multi-Signature, Factory|addr:0x7b8231f0652943734682c87c01d3169b19006c66|verified:true|block:23435295|tx:0xa8d6ffc5ebee557c8322359da0c626b8fd3e8e3987a9e860333b607beaa7a668|first_check:1758787533

Submitted on: 2025-09-25 10:05:33

Comments

Log in to comment.

No comments yet.