// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract AereHalo2CubicVerifier {
    uint256 internal constant    PROOF_LEN_CPTR = 0x44;
    uint256 internal constant        PROOF_CPTR = 0x64;
    uint256 internal constant NUM_INSTANCE_CPTR = 0x04e4;
    uint256 internal constant     INSTANCE_CPTR = 0x0504;

    uint256 internal constant FIRST_QUOTIENT_X_CPTR = 0x01a4;
    uint256 internal constant  LAST_QUOTIENT_X_CPTR = 0x0264;

    uint256 internal constant                VK_MPTR = 0x0320;
    uint256 internal constant         VK_DIGEST_MPTR = 0x0320;
    uint256 internal constant     NUM_INSTANCES_MPTR = 0x0340;
    uint256 internal constant                 K_MPTR = 0x0360;
    uint256 internal constant             N_INV_MPTR = 0x0380;
    uint256 internal constant             OMEGA_MPTR = 0x03a0;
    uint256 internal constant         OMEGA_INV_MPTR = 0x03c0;
    uint256 internal constant    OMEGA_INV_TO_L_MPTR = 0x03e0;
    uint256 internal constant   HAS_ACCUMULATOR_MPTR = 0x0400;
    uint256 internal constant        ACC_OFFSET_MPTR = 0x0420;
    uint256 internal constant     NUM_ACC_LIMBS_MPTR = 0x0440;
    uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = 0x0460;
    uint256 internal constant              G1_X_MPTR = 0x0480;
    uint256 internal constant              G1_Y_MPTR = 0x04a0;
    uint256 internal constant            G2_X_1_MPTR = 0x04c0;
    uint256 internal constant            G2_X_2_MPTR = 0x04e0;
    uint256 internal constant            G2_Y_1_MPTR = 0x0500;
    uint256 internal constant            G2_Y_2_MPTR = 0x0520;
    uint256 internal constant      NEG_S_G2_X_1_MPTR = 0x0540;
    uint256 internal constant      NEG_S_G2_X_2_MPTR = 0x0560;
    uint256 internal constant      NEG_S_G2_Y_1_MPTR = 0x0580;
    uint256 internal constant      NEG_S_G2_Y_2_MPTR = 0x05a0;

    uint256 internal constant CHALLENGE_MPTR = 0x07c0;

    uint256 internal constant THETA_MPTR = 0x07c0;
    uint256 internal constant  BETA_MPTR = 0x07e0;
    uint256 internal constant GAMMA_MPTR = 0x0800;
    uint256 internal constant     Y_MPTR = 0x0820;
    uint256 internal constant     X_MPTR = 0x0840;
    uint256 internal constant  ZETA_MPTR = 0x0860;
    uint256 internal constant    NU_MPTR = 0x0880;
    uint256 internal constant    MU_MPTR = 0x08a0;

    uint256 internal constant       ACC_LHS_X_MPTR = 0x08c0;
    uint256 internal constant       ACC_LHS_Y_MPTR = 0x08e0;
    uint256 internal constant       ACC_RHS_X_MPTR = 0x0900;
    uint256 internal constant       ACC_RHS_Y_MPTR = 0x0920;
    uint256 internal constant             X_N_MPTR = 0x0940;
    uint256 internal constant X_N_MINUS_1_INV_MPTR = 0x0960;
    uint256 internal constant          L_LAST_MPTR = 0x0980;
    uint256 internal constant         L_BLIND_MPTR = 0x09a0;
    uint256 internal constant             L_0_MPTR = 0x09c0;
    uint256 internal constant   INSTANCE_EVAL_MPTR = 0x09e0;
    uint256 internal constant   QUOTIENT_EVAL_MPTR = 0x0a00;
    uint256 internal constant      QUOTIENT_X_MPTR = 0x0a20;
    uint256 internal constant      QUOTIENT_Y_MPTR = 0x0a40;
    uint256 internal constant       G1_SCALAR_MPTR = 0x0a60;
    uint256 internal constant   PAIRING_LHS_X_MPTR = 0x0a80;
    uint256 internal constant   PAIRING_LHS_Y_MPTR = 0x0aa0;
    uint256 internal constant   PAIRING_RHS_X_MPTR = 0x0ac0;
    uint256 internal constant   PAIRING_RHS_Y_MPTR = 0x0ae0;

    function verifyProof(
        bytes calldata proof,
        uint256[] calldata instances
    ) public view returns (bool) {
        assembly {
            // Read EC point (x, y) at (proof_cptr, proof_cptr + 0x20),
            // and check if the point is on affine plane,
            // and store them in (hash_mptr, hash_mptr + 0x20).
            // Return updated (success, proof_cptr, hash_mptr).
            function read_ec_point(success, proof_cptr, hash_mptr, q) -> ret0, ret1, ret2 {
                let x := calldataload(proof_cptr)
                let y := calldataload(add(proof_cptr, 0x20))
                ret0 := and(success, lt(x, q))
                ret0 := and(ret0, lt(y, q))
                ret0 := and(ret0, eq(mulmod(y, y, q), addmod(mulmod(x, mulmod(x, x, q), q), 3, q)))
                mstore(hash_mptr, x)
                mstore(add(hash_mptr, 0x20), y)
                ret1 := add(proof_cptr, 0x40)
                ret2 := add(hash_mptr, 0x40)
            }

            // Squeeze challenge by keccak256(memory[0..hash_mptr]),
            // and store hash mod r as challenge in challenge_mptr,
            // and push back hash in 0x00 as the first input for next squeeze.
            // Return updated (challenge_mptr, hash_mptr).
            function squeeze_challenge(challenge_mptr, hash_mptr, r) -> ret0, ret1 {
                let hash := keccak256(0x00, hash_mptr)
                mstore(challenge_mptr, mod(hash, r))
                mstore(0x00, hash)
                ret0 := add(challenge_mptr, 0x20)
                ret1 := 0x20
            }

            // Squeeze challenge without absorbing new input from calldata,
            // by putting an extra 0x01 in memory[0x20] and squeeze by keccak256(memory[0..21]),
            // and store hash mod r as challenge in challenge_mptr,
            // and push back hash in 0x00 as the first input for next squeeze.
            // Return updated (challenge_mptr).
            function squeeze_challenge_cont(challenge_mptr, r) -> ret {
                mstore8(0x20, 0x01)
                let hash := keccak256(0x00, 0x21)
                mstore(challenge_mptr, mod(hash, r))
                mstore(0x00, hash)
                ret := add(challenge_mptr, 0x20)
            }

            // Batch invert values in memory[mptr_start..mptr_end] in place.
            // Return updated (success).
            function batch_invert(success, mptr_start, mptr_end, r) -> ret {
                let gp_mptr := mptr_end
                let gp := mload(mptr_start)
                let mptr := add(mptr_start, 0x20)
                for
                    {}
                    lt(mptr, sub(mptr_end, 0x20))
                    {}
                {
                    gp := mulmod(gp, mload(mptr), r)
                    mstore(gp_mptr, gp)
                    mptr := add(mptr, 0x20)
                    gp_mptr := add(gp_mptr, 0x20)
                }
                gp := mulmod(gp, mload(mptr), r)

                mstore(gp_mptr, 0x20)
                mstore(add(gp_mptr, 0x20), 0x20)
                mstore(add(gp_mptr, 0x40), 0x20)
                mstore(add(gp_mptr, 0x60), gp)
                mstore(add(gp_mptr, 0x80), sub(r, 2))
                mstore(add(gp_mptr, 0xa0), r)
                ret := and(success, staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20))
                let all_inv := mload(gp_mptr)

                let first_mptr := mptr_start
                let second_mptr := add(first_mptr, 0x20)
                gp_mptr := sub(gp_mptr, 0x20)
                for
                    {}
                    lt(second_mptr, mptr)
                    {}
                {
                    let inv := mulmod(all_inv, mload(gp_mptr), r)
                    all_inv := mulmod(all_inv, mload(mptr), r)
                    mstore(mptr, inv)
                    mptr := sub(mptr, 0x20)
                    gp_mptr := sub(gp_mptr, 0x20)
                }
                let inv_first := mulmod(all_inv, mload(second_mptr), r)
                let inv_second := mulmod(all_inv, mload(first_mptr), r)
                mstore(first_mptr, inv_first)
                mstore(second_mptr, inv_second)
            }

            // Add (x, y) into point at (0x00, 0x20).
            // Return updated (success).
            function ec_add_acc(success, x, y) -> ret {
                mstore(0x40, x)
                mstore(0x60, y)
                ret := and(success, staticcall(gas(), 0x06, 0x00, 0x80, 0x00, 0x40))
            }

            // Scale point at (0x00, 0x20) by scalar.
            function ec_mul_acc(success, scalar) -> ret {
                mstore(0x40, scalar)
                ret := and(success, staticcall(gas(), 0x07, 0x00, 0x60, 0x00, 0x40))
            }

            // Add (x, y) into point at (0x80, 0xa0).
            // Return updated (success).
            function ec_add_tmp(success, x, y) -> ret {
                mstore(0xc0, x)
                mstore(0xe0, y)
                ret := and(success, staticcall(gas(), 0x06, 0x80, 0x80, 0x80, 0x40))
            }

            // Scale point at (0x80, 0xa0) by scalar.
            // Return updated (success).
            function ec_mul_tmp(success, scalar) -> ret {
                mstore(0xc0, scalar)
                ret := and(success, staticcall(gas(), 0x07, 0x80, 0x60, 0x80, 0x40))
            }

            // Perform pairing check.
            // Return updated (success).
            function ec_pairing(success, lhs_x, lhs_y, rhs_x, rhs_y) -> ret {
                mstore(0x00, lhs_x)
                mstore(0x20, lhs_y)
                mstore(0x40, mload(G2_X_1_MPTR))
                mstore(0x60, mload(G2_X_2_MPTR))
                mstore(0x80, mload(G2_Y_1_MPTR))
                mstore(0xa0, mload(G2_Y_2_MPTR))
                mstore(0xc0, rhs_x)
                mstore(0xe0, rhs_y)
                mstore(0x100, mload(NEG_S_G2_X_1_MPTR))
                mstore(0x120, mload(NEG_S_G2_X_2_MPTR))
                mstore(0x140, mload(NEG_S_G2_Y_1_MPTR))
                mstore(0x160, mload(NEG_S_G2_Y_2_MPTR))
                ret := and(success, staticcall(gas(), 0x08, 0x00, 0x180, 0x00, 0x20))
                ret := and(ret, mload(0x00))
            }

            // Modulus
            let q := 21888242871839275222246405745257275088696311157297823662689037894645226208583 // BN254 base field
            let r := 21888242871839275222246405745257275088548364400416034343698204186575808495617 // BN254 scalar field

            // Initialize success as true
            let success := true

            {
                // Load vk_digest and num_instances of vk into memory
                mstore(0x0320, 0x11b8d7714d2fe38e4b8de74dfa5b27118ded5acb94e51d86774485048eaab68b) // vk_digest
                mstore(0x0340, 0x0000000000000000000000000000000000000000000000000000000000000001) // num_instances

                // Check valid length of proof
                success := and(success, eq(0x0480, calldataload(PROOF_LEN_CPTR)))

                // Check valid length of instances
                let num_instances := mload(NUM_INSTANCES_MPTR)
                success := and(success, eq(num_instances, calldataload(NUM_INSTANCE_CPTR)))

                // Absorb vk diegst
                mstore(0x00, mload(VK_DIGEST_MPTR))

                // Read instances and witness commitments and generate challenges
                let hash_mptr := 0x20
                let instance_cptr := INSTANCE_CPTR
                for
                    { let instance_cptr_end := add(instance_cptr, mul(0x20, num_instances)) }
                    lt(instance_cptr, instance_cptr_end)
                    {}
                {
                    let instance := calldataload(instance_cptr)
                    success := and(success, lt(instance, r))
                    mstore(hash_mptr, instance)
                    instance_cptr := add(instance_cptr, 0x20)
                    hash_mptr := add(hash_mptr, 0x20)
                }

                let proof_cptr := PROOF_CPTR
                let challenge_mptr := CHALLENGE_MPTR

                // Phase 1
                for
                    { let proof_cptr_end := add(proof_cptr, 0xc0) }
                    lt(proof_cptr, proof_cptr_end)
                    {}
                {
                    success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q)
                }

                challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r)
                challenge_mptr := squeeze_challenge_cont(challenge_mptr, r)
                challenge_mptr := squeeze_challenge_cont(challenge_mptr, r)

                // Phase 2
                for
                    { let proof_cptr_end := add(proof_cptr, 0x80) }
                    lt(proof_cptr, proof_cptr_end)
                    {}
                {
                    success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q)
                }

                challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r)

                // Phase 3
                for
                    { let proof_cptr_end := add(proof_cptr, 0x0100) }
                    lt(proof_cptr, proof_cptr_end)
                    {}
                {
                    success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q)
                }

                challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r)

                // Read evaluations
                for
                    { let proof_cptr_end := add(proof_cptr, 0x01c0) }
                    lt(proof_cptr, proof_cptr_end)
                    {}
                {
                    let eval := calldataload(proof_cptr)
                    success := and(success, lt(eval, r))
                    mstore(hash_mptr, eval)
                    proof_cptr := add(proof_cptr, 0x20)
                    hash_mptr := add(hash_mptr, 0x20)
                }

                // Read batch opening proof and generate challenges
                challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r)       // zeta
                challenge_mptr := squeeze_challenge_cont(challenge_mptr, r)                        // nu

                success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) // W

                challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r)       // mu

                success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) // W'

                // Load full vk into memory
                mstore(0x0320, 0x11b8d7714d2fe38e4b8de74dfa5b27118ded5acb94e51d86774485048eaab68b) // vk_digest
                mstore(0x0340, 0x0000000000000000000000000000000000000000000000000000000000000001) // num_instances
                mstore(0x0360, 0x000000000000000000000000000000000000000000000000000000000000000a) // k
                mstore(0x0380, 0x3058355f447953c1ade231a513e0f80710e9db4e679b02351f90fd168b040001) // n_inv
                mstore(0x03a0, 0x2ad9021ed07c42ab19f77c5cf2cbd2deb135ea330f1b1573bd08d99309c4bb7d) // omega
                mstore(0x03c0, 0x0ae3c95fc03c0a5f2de8a8f46c03ccdfdfed2bb98c9e4ae0b10b15eda4e3b1e3) // omega_inv
                mstore(0x03e0, 0x15f79db9c39181bc3e31c83f9291da76eedf1b23c410add7e9098464aaa4fb26) // omega_inv_to_l
                mstore(0x0400, 0x0000000000000000000000000000000000000000000000000000000000000000) // has_accumulator
                mstore(0x0420, 0x0000000000000000000000000000000000000000000000000000000000000000) // acc_offset
                mstore(0x0440, 0x0000000000000000000000000000000000000000000000000000000000000000) // num_acc_limbs
                mstore(0x0460, 0x0000000000000000000000000000000000000000000000000000000000000000) // num_acc_limb_bits
                mstore(0x0480, 0x0000000000000000000000000000000000000000000000000000000000000001) // g1_x
                mstore(0x04a0, 0x0000000000000000000000000000000000000000000000000000000000000002) // g1_y
                mstore(0x04c0, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) // g2_x_1
                mstore(0x04e0, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) // g2_x_2
                mstore(0x0500, 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) // g2_y_1
                mstore(0x0520, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) // g2_y_2
                mstore(0x0540, 0x12c0e8d2fae98104fcd44c7e31e3b66f61e404949cb079aeef10b75c665db892) // neg_s_g2_x_1
                mstore(0x0560, 0x179c7e86619a91461751256c782b6200c9bb6e70beb8c90d206b956b92235e28) // neg_s_g2_x_2
                mstore(0x0580, 0x0a0c9027cee6bb59d2f3459a8c5fd59a02b32f5a012a8221b7cffcb4d676f56f) // neg_s_g2_y_1
                mstore(0x05a0, 0x2f5f9ab0af884ceb2222b6d3826751366d38486be711f7e9929fbc0777fac256) // neg_s_g2_y_2
                mstore(0x05c0, 0x018ae5cff801e7035bab74d7c7c8837ee34dc88354ec6a6ec3e4ea94ffc5b31e) // fixed_comms[0].x
                mstore(0x05e0, 0x1b881cd510a49b060b48d563340cee9a406b636114a29ca72a43a1fd7b833566) // fixed_comms[0].y
                mstore(0x0600, 0x2796320633288d633427b3b200db8f34a146ce05ac2b9c55223da5c7a0dbbf79) // fixed_comms[1].x
                mstore(0x0620, 0x2c94fd3f66b10ba737247338c1688eda12db66e66a64d596b0c0e6190ac88357) // fixed_comms[1].y
                mstore(0x0640, 0x2533b1eae2d4f10a32ba520bf9716c0961824891a9c3b87e766d64a5bffb1e40) // fixed_comms[2].x
                mstore(0x0660, 0x1120d5179bd78c44ed4b286ec5f5a46bffd06a6396611982fcc2b2a05b0c138c) // fixed_comms[2].y
                mstore(0x0680, 0x08b4c37280e855fd45ae292c16a0c5be2ec2e9203652b3de358175108ce68bb8) // fixed_comms[3].x
                mstore(0x06a0, 0x17bec8b8d337980c5a7611eab611fd353ac2d3b69c935d6e25827722e7e65ce2) // fixed_comms[3].y
                mstore(0x06c0, 0x2a275f0773f0047bdede17e8fb0359fc96826efc6dcc1bd3d52632bb53bcad6d) // fixed_comms[4].x
                mstore(0x06e0, 0x1065e5809bbfbbb9ca99e1b56daab043cf2bc3310385bb46a8202ddaf42291d5) // fixed_comms[4].y
                mstore(0x0700, 0x2c30689dfa71d55014d238f1280183844517e99d4bbee5824c8d49b76b5304b1) // permutation_comms[0].x
                mstore(0x0720, 0x1d52e508e8605ef882357a21ea8b6bb2f42a84eae6de1c28ae0d2921346df6b5) // permutation_comms[0].y
                mstore(0x0740, 0x1a62f5081b33e431fb050a901291ee7bec2005e2a2cff727f46dc2e22bf14128) // permutation_comms[1].x
                mstore(0x0760, 0x0211cac7165924093fdfbb20e793dcea288181ba24d854c3dcc838a52d607a86) // permutation_comms[1].y
                mstore(0x0780, 0x2691f98a2c52172f0ccf930d12afbe07ad981c7d1817d819071cf25da892ae5b) // permutation_comms[2].x
                mstore(0x07a0, 0x1fdce525572200d2cb5e8dd7170bc339c98b992971ec3255e2e87f2670be6157) // permutation_comms[2].y

                // Read accumulator from instances
                if mload(HAS_ACCUMULATOR_MPTR) {
                    let num_limbs := mload(NUM_ACC_LIMBS_MPTR)
                    let num_limb_bits := mload(NUM_ACC_LIMB_BITS_MPTR)

                    let cptr := add(INSTANCE_CPTR, mul(mload(ACC_OFFSET_MPTR), 0x20))
                    let lhs_y_off := mul(num_limbs, 0x20)
                    let rhs_x_off := mul(lhs_y_off, 2)
                    let rhs_y_off := mul(lhs_y_off, 3)
                    let lhs_x := calldataload(cptr)
                    let lhs_y := calldataload(add(cptr, lhs_y_off))
                    let rhs_x := calldataload(add(cptr, rhs_x_off))
                    let rhs_y := calldataload(add(cptr, rhs_y_off))
                    for
                        {
                            let cptr_end := add(cptr, mul(0x20, num_limbs))
                            let shift := num_limb_bits
                        }
                        lt(cptr, cptr_end)
                        {}
                    {
                        cptr := add(cptr, 0x20)
                        lhs_x := add(lhs_x, shl(shift, calldataload(cptr)))
                        lhs_y := add(lhs_y, shl(shift, calldataload(add(cptr, lhs_y_off))))
                        rhs_x := add(rhs_x, shl(shift, calldataload(add(cptr, rhs_x_off))))
                        rhs_y := add(rhs_y, shl(shift, calldataload(add(cptr, rhs_y_off))))
                        shift := add(shift, num_limb_bits)
                    }

                    success := and(success, and(lt(lhs_x, q), lt(lhs_y, q)))
                    success := and(success, eq(mulmod(lhs_y, lhs_y, q), addmod(mulmod(lhs_x, mulmod(lhs_x, lhs_x, q), q), 3, q)))
                    success := and(success, and(lt(rhs_x, q), lt(rhs_y, q)))
                    success := and(success, eq(mulmod(rhs_y, rhs_y, q), addmod(mulmod(rhs_x, mulmod(rhs_x, rhs_x, q), q), 3, q)))

                    mstore(ACC_LHS_X_MPTR, lhs_x)
                    mstore(ACC_LHS_Y_MPTR, lhs_y)
                    mstore(ACC_RHS_X_MPTR, rhs_x)
                    mstore(ACC_RHS_Y_MPTR, rhs_y)
                }

                pop(q)
            }

            // Revert earlier if anything from calldata is invalid
            if iszero(success) {
                revert(0, 0)
            }

            // Compute lagrange evaluations and instance evaluation
            {
                let k := mload(K_MPTR)
                let x := mload(X_MPTR)
                let x_n := x
                for
                    { let idx := 0 }
                    lt(idx, k)
                    { idx := add(idx, 1) }
                {
                    x_n := mulmod(x_n, x_n, r)
                }

                let omega := mload(OMEGA_MPTR)

                let mptr := X_N_MPTR
                let mptr_end := add(mptr, mul(0x20, add(mload(NUM_INSTANCES_MPTR), 6)))
                if iszero(mload(NUM_INSTANCES_MPTR)) {
                    mptr_end := add(mptr_end, 0x20)
                }
                for
                    { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) }
                    lt(mptr, mptr_end)
                    { mptr := add(mptr, 0x20) }
                {
                    mstore(mptr, addmod(x, sub(r, pow_of_omega), r))
                    pow_of_omega := mulmod(pow_of_omega, omega, r)
                }
                let x_n_minus_1 := addmod(x_n, sub(r, 1), r)
                mstore(mptr_end, x_n_minus_1)
                success := batch_invert(success, X_N_MPTR, add(mptr_end, 0x20), r)

                mptr := X_N_MPTR
                let l_i_common := mulmod(x_n_minus_1, mload(N_INV_MPTR), r)
                for
                    { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) }
                    lt(mptr, mptr_end)
                    { mptr := add(mptr, 0x20) }
                {
                    mstore(mptr, mulmod(l_i_common, mulmod(mload(mptr), pow_of_omega, r), r))
                    pow_of_omega := mulmod(pow_of_omega, omega, r)
                }

                let l_blind := mload(add(X_N_MPTR, 0x20))
                let l_i_cptr := add(X_N_MPTR, 0x40)
                for
                    { let l_i_cptr_end := add(X_N_MPTR, 0xc0) }
                    lt(l_i_cptr, l_i_cptr_end)
                    { l_i_cptr := add(l_i_cptr, 0x20) }
                {
                    l_blind := addmod(l_blind, mload(l_i_cptr), r)
                }

                let instance_eval := 0
                for
                    {
                        let instance_cptr := INSTANCE_CPTR
                        let instance_cptr_end := add(instance_cptr, mul(0x20, mload(NUM_INSTANCES_MPTR)))
                    }
                    lt(instance_cptr, instance_cptr_end)
                    {
                        instance_cptr := add(instance_cptr, 0x20)
                        l_i_cptr := add(l_i_cptr, 0x20)
                    }
                {
                    instance_eval := addmod(instance_eval, mulmod(mload(l_i_cptr), calldataload(instance_cptr), r), r)
                }

                let x_n_minus_1_inv := mload(mptr_end)
                let l_last := mload(X_N_MPTR)
                let l_0 := mload(add(X_N_MPTR, 0xc0))

                mstore(X_N_MPTR, x_n)
                mstore(X_N_MINUS_1_INV_MPTR, x_n_minus_1_inv)
                mstore(L_LAST_MPTR, l_last)
                mstore(L_BLIND_MPTR, l_blind)
                mstore(L_0_MPTR, l_0)
                mstore(INSTANCE_EVAL_MPTR, instance_eval)
            }

            // Compute quotient evavluation
            {
                let quotient_eval_numer
                let delta := 4131629893567559867359510883348571134090853742863529169391034518566172092834
                let y := mload(Y_MPTR)
                {
                    let f_0 := calldataload(0x0304)
                    let a_0 := calldataload(0x02a4)
                    let var0 := mulmod(f_0, a_0, r)
                    let f_1 := calldataload(0x0324)
                    let a_1 := calldataload(0x02c4)
                    let var1 := mulmod(f_1, a_1, r)
                    let var2 := addmod(var0, var1, r)
                    let f_2 := calldataload(0x0344)
                    let a_2 := calldataload(0x02e4)
                    let var3 := mulmod(f_2, a_2, r)
                    let var4 := addmod(var2, var3, r)
                    let f_3 := calldataload(0x0364)
                    let var5 := mulmod(f_3, a_0, r)
                    let var6 := mulmod(var5, a_1, r)
                    let var7 := addmod(var4, var6, r)
                    let f_4 := calldataload(0x0384)
                    let var8 := addmod(var7, f_4, r)
                    let i_eval := mload(INSTANCE_EVAL_MPTR)
                    let var9 := addmod(var8, i_eval, r)
                    quotient_eval_numer := var9
                }
                {
                    let l_0 := mload(L_0_MPTR)
                    let eval := addmod(l_0, sub(r, mulmod(l_0, calldataload(0x0424), r)), r)
                    quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r)
                }
                {
                    let perm_z_last := calldataload(0x0424)
                    let eval := mulmod(mload(L_LAST_MPTR), addmod(mulmod(perm_z_last, perm_z_last, r), sub(r, perm_z_last), r), r)
                    quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r)
                }
                {
                    let gamma := mload(GAMMA_MPTR)
                    let beta := mload(BETA_MPTR)
                    let lhs := calldataload(0x0444)
                    let rhs := calldataload(0x0424)
                    lhs := mulmod(lhs, addmod(addmod(calldataload(0x02a4), mulmod(beta, calldataload(0x03c4), r), r), gamma, r), r)
                    lhs := mulmod(lhs, addmod(addmod(calldataload(0x02c4), mulmod(beta, calldataload(0x03e4), r), r), gamma, r), r)
                    lhs := mulmod(lhs, addmod(addmod(calldataload(0x02e4), mulmod(beta, calldataload(0x0404), r), r), gamma, r), r)
                    mstore(0x00, mulmod(beta, mload(X_MPTR), r))
                    rhs := mulmod(rhs, addmod(addmod(calldataload(0x02a4), mload(0x00), r), gamma, r), r)
                    mstore(0x00, mulmod(mload(0x00), delta, r))
                    rhs := mulmod(rhs, addmod(addmod(calldataload(0x02c4), mload(0x00), r), gamma, r), r)
                    mstore(0x00, mulmod(mload(0x00), delta, r))
                    rhs := mulmod(rhs, addmod(addmod(calldataload(0x02e4), mload(0x00), r), gamma, r), r)
                    let left_sub_right := addmod(lhs, sub(r, rhs), r)
                    let eval := addmod(left_sub_right, sub(r, mulmod(left_sub_right, addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r), r)), r)
                    quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), eval, r)
                }

                pop(y)
                pop(delta)

                let quotient_eval := mulmod(quotient_eval_numer, mload(X_N_MINUS_1_INV_MPTR), r)
                mstore(QUOTIENT_EVAL_MPTR, quotient_eval)
            }

            // Compute quotient commitment
            {
                mstore(0x00, calldataload(LAST_QUOTIENT_X_CPTR))
                mstore(0x20, calldataload(add(LAST_QUOTIENT_X_CPTR, 0x20)))
                let x_n := mload(X_N_MPTR)
                for
                    {
                        let cptr := sub(LAST_QUOTIENT_X_CPTR, 0x40)
                        let cptr_end := sub(FIRST_QUOTIENT_X_CPTR, 0x40)
                    }
                    lt(cptr_end, cptr)
                    {}
                {
                    success := ec_mul_acc(success, x_n)
                    success := ec_add_acc(success, calldataload(cptr), calldataload(add(cptr, 0x20)))
                    cptr := sub(cptr, 0x40)
                }
                mstore(QUOTIENT_X_MPTR, mload(0x00))
                mstore(QUOTIENT_Y_MPTR, mload(0x20))
            }

            // Compute pairing lhs and rhs
            {
                {
                    let x := mload(X_MPTR)
                    let omega := mload(OMEGA_MPTR)
                    let omega_inv := mload(OMEGA_INV_MPTR)
                    let x_pow_of_omega := mulmod(x, omega, r)
                    mstore(0x01e0, x_pow_of_omega)
                    mstore(0x01c0, x)
                    x_pow_of_omega := mulmod(x, omega_inv, r)
                }
                {
                    let mu := mload(MU_MPTR)
                    for
                        {
                            let mptr := 0x0200
                            let mptr_end := 0x0240
                            let point_mptr := 0x01c0
                        }
                        lt(mptr, mptr_end)
                        {
                            mptr := add(mptr, 0x20)
                            point_mptr := add(point_mptr, 0x20)
                        }
                    {
                        mstore(mptr, addmod(mu, sub(r, mload(point_mptr)), r))
                    }
                    let s
                    s := mload(0x0200)
                    mstore(0x0240, s)
                    let diff
                    diff := mload(0x0220)
                    mstore(0x0260, diff)
                    mstore(0x00, diff)
                    diff := 1
                    mstore(0x0280, diff)
                }
                {
                    let point_0 := mload(0x01c0)
                    let coeff
                    coeff := 1
                    coeff := mulmod(coeff, mload(0x0200), r)
                    mstore(0x20, coeff)
                }
                {
                    let point_0 := mload(0x01c0)
                    let point_1 := mload(0x01e0)
                    let coeff
                    coeff := addmod(point_0, sub(r, point_1), r)
                    coeff := mulmod(coeff, mload(0x0200), r)
                    mstore(0x40, coeff)
                    coeff := addmod(point_1, sub(r, point_0), r)
                    coeff := mulmod(coeff, mload(0x0220), r)
                    mstore(0x60, coeff)
                }
                {
                    success := batch_invert(success, 0, 0x80, r)
                    let diff_0_inv := mload(0x00)
                    mstore(0x0260, diff_0_inv)
                    for
                        {
                            let mptr := 0x0280
                            let mptr_end := 0x02a0
                        }
                        lt(mptr, mptr_end)
                        { mptr := add(mptr, 0x20) }
                    {
                        mstore(mptr, mulmod(mload(mptr), diff_0_inv, r))
                    }
                }
                {
                    let coeff := mload(0x20)
                    let zeta := mload(ZETA_MPTR)
                    let r_eval
                    r_eval := mulmod(coeff, calldataload(0x03a4), r)
                    r_eval := mulmod(r_eval, zeta, r)
                    r_eval := addmod(r_eval, mulmod(coeff, mload(QUOTIENT_EVAL_MPTR), r), r)
                    for
                        {
                            let cptr := 0x0404
                            let cptr_end := 0x03a4
                        }
                        lt(cptr_end, cptr)
                        { cptr := sub(cptr, 0x20) }
                    {
                        r_eval := addmod(mulmod(r_eval, zeta, r), mulmod(coeff, calldataload(cptr), r), r)
                    }
                    for
                        {
                            let cptr := 0x0384
                            let cptr_end := 0x0284
                        }
                        lt(cptr_end, cptr)
                        { cptr := sub(cptr, 0x20) }
                    {
                        r_eval := addmod(mulmod(r_eval, zeta, r), mulmod(coeff, calldataload(cptr), r), r)
                    }
                    mstore(0x02a0, r_eval)
                }
                {
                    let zeta := mload(ZETA_MPTR)
                    let r_eval
                    r_eval := addmod(r_eval, mulmod(mload(0x40), calldataload(0x0424), r), r)
                    r_eval := addmod(r_eval, mulmod(mload(0x60), calldataload(0x0444), r), r)
                    r_eval := mulmod(r_eval, mload(0x0280), r)
                    mstore(0x02c0, r_eval)
                }
                {
                    let sum := mload(0x20)
                    mstore(0x02e0, sum)
                }
                {
                    let sum := mload(0x40)
                    sum := addmod(sum, mload(0x60), r)
                    mstore(0x0300, sum)
                }
                {
                    for
                        {
                            let mptr := 0x00
                            let mptr_end := 0x40
                            let sum_mptr := 0x02e0
                        }
                        lt(mptr, mptr_end)
                        {
                            mptr := add(mptr, 0x20)
                            sum_mptr := add(sum_mptr, 0x20)
                        }
                    {
                        mstore(mptr, mload(sum_mptr))
                    }
                    success := batch_invert(success, 0, 0x40, r)
                    let r_eval := mulmod(mload(0x20), mload(0x02c0), r)
                    for
                        {
                            let sum_inv_mptr := 0x00
                            let sum_inv_mptr_end := 0x40
                            let r_eval_mptr := 0x02a0
                        }
                        lt(sum_inv_mptr, sum_inv_mptr_end)
                        {
                            sum_inv_mptr := sub(sum_inv_mptr, 0x20)
                            r_eval_mptr := sub(r_eval_mptr, 0x20)
                        }
                    {
                        r_eval := mulmod(r_eval, mload(NU_MPTR), r)
                        r_eval := addmod(r_eval, mulmod(mload(sum_inv_mptr), mload(r_eval_mptr), r), r)
                    }
                    mstore(G1_SCALAR_MPTR, sub(r, r_eval))
                }
                {
                    let zeta := mload(ZETA_MPTR)
                    let nu := mload(NU_MPTR)
                    mstore(0x00, calldataload(0x0164))
                    mstore(0x20, calldataload(0x0184))
                    success := ec_mul_acc(success, zeta)
                    success := ec_add_acc(success, mload(QUOTIENT_X_MPTR), mload(QUOTIENT_Y_MPTR))
                    for
                        {
                            let ptr := 0x0780
                            let ptr_end := 0x0580
                        }
                        lt(ptr_end, ptr)
                        { ptr := sub(ptr, 0x40) }
                    {
                        success := ec_mul_acc(success, zeta)
                        success := ec_add_acc(success, mload(ptr), mload(add(ptr, 0x20)))
                    }
                    for
                        {
                            let ptr := 0xe4
                            let ptr_end := 0x24
                        }
                        lt(ptr_end, ptr)
                        { ptr := sub(ptr, 0x40) }
                    {
                        success := ec_mul_acc(success, zeta)
                        success := ec_add_acc(success, calldataload(ptr), calldataload(add(ptr, 0x20)))
                    }
                    mstore(0x80, calldataload(0x0124))
                    mstore(0xa0, calldataload(0x0144))
                    success := ec_mul_tmp(success, mulmod(nu, mload(0x0280), r))
                    success := ec_add_acc(success, mload(0x80), mload(0xa0))
                    mstore(0x80, mload(G1_X_MPTR))
                    mstore(0xa0, mload(G1_Y_MPTR))
                    success := ec_mul_tmp(success, mload(G1_SCALAR_MPTR))
                    success := ec_add_acc(success, mload(0x80), mload(0xa0))
                    mstore(0x80, calldataload(0x0464))
                    mstore(0xa0, calldataload(0x0484))
                    success := ec_mul_tmp(success, sub(r, mload(0x0240)))
                    success := ec_add_acc(success, mload(0x80), mload(0xa0))
                    mstore(0x80, calldataload(0x04a4))
                    mstore(0xa0, calldataload(0x04c4))
                    success := ec_mul_tmp(success, mload(MU_MPTR))
                    success := ec_add_acc(success, mload(0x80), mload(0xa0))
                    mstore(PAIRING_LHS_X_MPTR, mload(0x00))
                    mstore(PAIRING_LHS_Y_MPTR, mload(0x20))
                    mstore(PAIRING_RHS_X_MPTR, calldataload(0x04a4))
                    mstore(PAIRING_RHS_Y_MPTR, calldataload(0x04c4))
                }
            }

            // Random linear combine with accumulator
            if mload(HAS_ACCUMULATOR_MPTR) {
                mstore(0x00, mload(ACC_LHS_X_MPTR))
                mstore(0x20, mload(ACC_LHS_Y_MPTR))
                mstore(0x40, mload(ACC_RHS_X_MPTR))
                mstore(0x60, mload(ACC_RHS_Y_MPTR))
                mstore(0x80, mload(PAIRING_LHS_X_MPTR))
                mstore(0xa0, mload(PAIRING_LHS_Y_MPTR))
                mstore(0xc0, mload(PAIRING_RHS_X_MPTR))
                mstore(0xe0, mload(PAIRING_RHS_Y_MPTR))
                let challenge := mod(keccak256(0x00, 0x100), r)

                // [pairing_lhs] += challenge * [acc_lhs]
                success := ec_mul_acc(success, challenge)
                success := ec_add_acc(success, mload(PAIRING_LHS_X_MPTR), mload(PAIRING_LHS_Y_MPTR))
                mstore(PAIRING_LHS_X_MPTR, mload(0x00))
                mstore(PAIRING_LHS_Y_MPTR, mload(0x20))

                // [pairing_rhs] += challenge * [acc_rhs]
                mstore(0x00, mload(ACC_RHS_X_MPTR))
                mstore(0x20, mload(ACC_RHS_Y_MPTR))
                success := ec_mul_acc(success, challenge)
                success := ec_add_acc(success, mload(PAIRING_RHS_X_MPTR), mload(PAIRING_RHS_Y_MPTR))
                mstore(PAIRING_RHS_X_MPTR, mload(0x00))
                mstore(PAIRING_RHS_Y_MPTR, mload(0x20))
            }

            // Perform pairing
            success := ec_pairing(
                success,
                mload(PAIRING_LHS_X_MPTR),
                mload(PAIRING_LHS_Y_MPTR),
                mload(PAIRING_RHS_X_MPTR),
                mload(PAIRING_RHS_Y_MPTR)
            )

            // Revert if anything fails
            if iszero(success) {
                revert(0x00, 0x00)
            }

            // Return 1 as result if everything succeeds
            mstore(0x00, 1)
            return(0x00, 0x20)
        }
    }
}