module {{named_address}}::{{module_name}} {
    use aptos_std::bn254_algebra::{
        Fr as Bn254Fr,
        FormatFrLsb as Bn254FormatFrLsb,
        FormatG1Uncompr as Bn254FormatG1Uncompr,
        FormatG2Uncompr as Bn254FormatG2Uncompr,
        G1 as Bn254G1,
        G2 as Bn254G2,
        Gt as Bn254Gt,
    };
    use aptos_std::crypto_algebra::{
        deserialize as deserialize_element,
        Element,
        add,
        eq,
        multi_scalar_mul,
        pairing,
        zero,
    };
    use std::option;
    use std::vector;

    const EINVALID_PROOF: u64 = 1;

    fun vk_alpha_g1_bytes(): vector<u8> { {{{vk_alpha_g1}}} }
    fun vk_beta_g2_bytes(): vector<u8> { {{{vk_beta_g2}}} }
    fun vk_gamma_g2_bytes(): vector<u8> { {{{vk_gamma_g2}}} }
    fun vk_delta_g2_bytes(): vector<u8> { {{{vk_delta_g2}}} }
    fun vk_gamma_abc_g1_bytes(): vector<vector<u8>> { {{{vk_gamma_abc_g1_rendered}}} }

    fun vk_alpha_g1(): Element<Bn254G1> {
        deserialize_or_abort_bn254_g1(&vk_alpha_g1_bytes())
    }

    fun vk_beta_g2(): Element<Bn254G2> {
        deserialize_or_abort_bn254_g2(&vk_beta_g2_bytes())
    }

    fun vk_gamma_g2(): Element<Bn254G2> {
        deserialize_or_abort_bn254_g2(&vk_gamma_g2_bytes())
    }

    fun vk_delta_g2(): Element<Bn254G2> {
        deserialize_or_abort_bn254_g2(&vk_delta_g2_bytes())
    }

    fun vk_gamma_abc_g1(): vector<Element<Bn254G1>> {
        let result: vector<Element<Bn254G1>> = vector[];
        let bytes = vk_gamma_abc_g1_bytes();
        let idx = 0;
        while (idx < vector::length(&bytes)) {
            vector::push_back(
                &mut result,
                deserialize_or_abort_bn254_g1(vector::borrow(&bytes, idx)),
            );
            idx = idx + 1;
        };
        result
    }

    fun vk_gamma_abc_commitment(
        public_inputs: vector<vector<u8>>,
        ic: vector<Element<Bn254G1>>,
    ): (Element<Bn254G1>, bool) {
        if (vector::length(&public_inputs) + 1 != vector::length(&ic)) {
            return (zero<Bn254G1>(), false);
        };

        let acc = *vector::borrow(&ic, 0);
        if (!vector::is_empty(&public_inputs)) {
            let idx = 0;
            while (idx < vector::length(&public_inputs)) {
                let scalar = deserialize_or_abort_bn254_fr(vector::borrow(&public_inputs, idx));
                let points: vector<Element<Bn254G1>> = vector[];
                vector::push_back(&mut points, *vector::borrow(&ic, idx + 1));
                let scalars: vector<Element<Bn254Fr>> = vector[];
                vector::push_back(&mut scalars, scalar);
                let scaled = multi_scalar_mul(&points, &scalars);
                acc = add(&acc, &scaled);
                idx = idx + 1;
            };
        };
        (acc, true)
    }

    public fun verify(
        public_inputs: vector<vector<u8>>,
        proof_a: vector<u8>,
        proof_b: vector<u8>,
        proof_c: vector<u8>,
    ): bool {
        let ic = vk_gamma_abc_g1();
        let (commitments, valid_inputs) = vk_gamma_abc_commitment(public_inputs, ic);
        if (!valid_inputs) {
            return false;
        };
        let a = deserialize_or_abort_bn254_g1(&proof_a);
        let b = deserialize_or_abort_bn254_g2(&proof_b);
        let c = deserialize_or_abort_bn254_g1(&proof_c);

        let expected = pairing<Bn254G1, Bn254G2, Bn254Gt>(&vk_alpha_g1(), &vk_beta_g2());
        expected = add(&expected, &pairing<Bn254G1, Bn254G2, Bn254Gt>(&commitments, &vk_gamma_g2()));
        expected = add(&expected, &pairing<Bn254G1, Bn254G2, Bn254Gt>(&c, &vk_delta_g2()));

        let lhs = pairing<Bn254G1, Bn254G2, Bn254Gt>(&a, &b);
        eq(&lhs, &expected)
    }

    {{#if include_entry}}
    public entry fun verify_entry(
        _signer: &signer,
        public_inputs: vector<vector<u8>>,
        proof_a: vector<u8>,
        proof_b: vector<u8>,
        proof_c: vector<u8>,
    ) {
        assert!(verify(public_inputs, proof_a, proof_b, proof_c), EINVALID_PROOF);
    }
    {{/if}}

    fun deserialize_or_abort_bn254_g1(value: &vector<u8>): Element<Bn254G1> {
        let point = deserialize_element<Bn254G1, Bn254FormatG1Uncompr>(value);
        if (option::is_none(&point)) {
            abort EINVALID_PROOF;
        };
        option::destroy_some(point)
    }

    fun deserialize_or_abort_bn254_g2(value: &vector<u8>): Element<Bn254G2> {
        let point = deserialize_element<Bn254G2, Bn254FormatG2Uncompr>(value);
        if (option::is_none(&point)) {
            abort EINVALID_PROOF;
        };
        option::destroy_some(point)
    }

    fun deserialize_or_abort_bn254_fr(value: &vector<u8>): Element<Bn254Fr> {
        let scalar = deserialize_element<Bn254Fr, Bn254FormatFrLsb>(value);
        if (option::is_none(&scalar)) {
            abort EINVALID_PROOF;
        };
        option::destroy_some(scalar)
    }
}
