{"id":13815937,"url":"https://github.com/scroll-tech/halo2-snark-aggregator","last_synced_at":"2025-05-15T12:31:08.414Z","repository":{"id":37379021,"uuid":"409256414","full_name":"scroll-tech/halo2-snark-aggregator","owner":"scroll-tech","description":"halo2 ecc circuit","archived":true,"fork":false,"pushed_at":"2023-02-17T01:56:09.000Z","size":1430,"stargazers_count":114,"open_issues_count":18,"forks_count":24,"subscribers_count":24,"default_branch":"main","last_synced_at":"2024-11-19T12:49:31.413Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scroll-tech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-09-22T15:23:57.000Z","updated_at":"2024-09-24T01:14:51.000Z","dependencies_parsed_at":"2024-01-13T15:37:31.511Z","dependency_job_id":"6725e319-a669-4598-839e-b6a099616402","html_url":"https://github.com/scroll-tech/halo2-snark-aggregator","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scroll-tech%2Fhalo2-snark-aggregator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scroll-tech%2Fhalo2-snark-aggregator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scroll-tech%2Fhalo2-snark-aggregator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scroll-tech%2Fhalo2-snark-aggregator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scroll-tech","download_url":"https://codeload.github.com/scroll-tech/halo2-snark-aggregator/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254340970,"owners_count":22054954,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T05:00:24.401Z","updated_at":"2025-05-15T12:31:07.678Z","avatar_url":"https://github.com/scroll-tech.png","language":"Rust","funding_links":[],"categories":["Gadgets"],"sub_categories":[],"readme":"# Halo2 Circuit Aggregation\n\n## 1 Schema:\n\n### 1.1 Query with commits and evaluations.\n\nGiven polynomial $p$, we denote $p(X)$ (captial X) to be the polynomial itself, [p] to be the commit of $p$ and $p(v)$ to be the evaluation of $p$ at point $v$. A Evaluation Query of polynomial p is a tuple $(z,p(z),[p])$. For all queries, we group them into groups so that every query in the same group are evaluated at the same point $z_i$. It follows that the main body of the verifying process is to calculate Queries $Q(z,p(z), [p])$ for various $p$ and then group them as above and then figure out the final multiopen formula.\n\nThus a proof and verification **schema** can be defined as a large multi-open of queries.\n\nFor example we can write down the verifying schema of standard plonk in some ast in the following AST:\n\n```\n        let r = eval!(a) * eval!(b) * commit!(qm) + eval!(a) * commit!(ql)\n            + eval!(b) * commit!(qr) + eval!(c) * commit!(qo) + pi_xi + commit!(qc)\n            + scalar!(self.alpha) * (\n                  (eval!(a) + (scalar!(self.beta) * xi.clone()) + scalar!(self.gamma))\n                * (eval!(b) + (scalar!(self.beta)\n                        * scalar!(self.common.k[0]) * xi.clone()) + scalar!(self.gamma))\n                * (eval!(c) + (scalar!(self.beta)\n                        * scalar!(self.common.k[1]) * xi) + scalar!(self.gamma))\n                * commit!(z)\n                + (eval!(a) + (scalar!(self.beta) * eval!(sigma1))\n                    + scalar!(self.gamma))\n                * (eval!(b) + (scalar!(self.beta) * eval!(sigma2))\n                    + scalar!(self.gamma))\n                * (eval!(c) + (scalar!(self.beta) * commit!(sigma3))\n                    + scalar!(self.gamma))\n                * eval!(zxi)\n              )\n            + scalar!(self.alpha) * scalar!(self.alpha)\n                * l1_xi * (commit!(z) + scalar!(neg_one))\n            + zh_xi * (commit!(tl) + xi_n * commit!(tm) + xi_2n * commit!(th))\n            + scalar!(self.v) * (\n                commit!(a) + scalar!(self.v) * (\n                    commit!(b) + scalar!(self.v) * (\n                        commit!(c) + scalar!(self.v) * (\n                            commit!(sigma1) + scalar!(self.v) * commit!(sigma2)\n                        )\n                    )\n                )\n            )\n            + scalar!(self.v) * (\n                eval!(a) + scalar!(self.v) * (\n                    eval!(b) + scalar!(self.v) * (\n                        eval!(c) + scalar!(self.v) * (\n                            eval!(sigma1) + scalar!(self.v) * eval!(sigma2)\n                        )\n                    )\n                )\n            );\n        Ok(EvaluationProof {s:r, point:self.xi.clone(), w: self.commits.w_z})\n```\n\n### 1.2 Arithment within special context:\nThis document is a design brief of how to generate plonk verifying code and plonk verification circuit (mainly for proof aggregation) at the same time. The main idea is to abstract a trait of group and field algebra for both code and circuit(We believe there are various benifits to do so both for maintaince and for furture updating of the proof schema for the verifying circuit). To achieve this, we notice that in verifying code generation, arithment are performed with no side effects but when using fp over fr arithment gates, integer and pointer arithment will have side effects(change the state of the gate).\n\nThus our arithment trait is a trait that will assume side effects do exists during the arithment of scalar fields, base fields and point groups of ecc.\n\nFor example, arithment like \"a + b * p\" needs to be performed under a arith_in_ctx macro looks like\n```\narith_in_ctx!([ctx], a + b * p)\n```\n\nMoreover the context has to components, one for scalar arithment and the other for elliptic arithment.\n\n```\npub trait ArithEccChip:\n    ArithCommonChip\u003cValue = Self::Point, AssignedValue = Self::AssignedPoint\u003e\n{\n\n    type ScalarChip: ArithFieldChip\u003c\n        Context = Self::Context,\n        Field = Self::Scalar,\n        AssignedField = Self::AssignedScalar,\n        Error = Self::Error,\n    \u003e;\n    type NativeChip: ArithFieldChip\u003c\n        Context = Self::Context,\n        Field = Self::Native,\n        AssignedField = Self::AssignedNative,\n        Error = Self::Error,\n    \u003e;\n\n```\n\n### 1.3 Different context for schema:\nThere are three computation context involved in the codebase:\n*    Pure calculation context\n*    Halo2 circuit context\n*    Solidity runtime context\n\nWhen basic arithment are implemented for all these three context, we implement the eval trait of **schema** to evaluate the result of the mutl_exp\n\n```\nimpl\u003cP: Clone, S: Clone\u003e EvaluationQuerySchema\u003cP, S\u003e {\n    pub fn eval\u003c\n        Scalar: FieldExt,\n        A: ArithEccChip\u003cAssignedPoint = P, AssignedScalar = S, Scalar = Scalar\u003e,\n    \u003e(\n        self,\n        ctx: \u0026mut A::Context,\n        schip: \u0026A::ScalarChip,\n        pchip: \u0026A,\n        one: \u0026A::AssignedScalar,\n    ) -\u003e Result\u003c(A::AssignedPoint, Option\u003cA::AssignedScalar\u003e), A::Error\u003e {\n\n\n```\n\nBased on this trait, we will be able to implement some universal code base for verifying code (executable files to generate witness), verifying circuit(can be used for proof aggregator) and solidity code.\n\n\nthen by providing different trait implementation of scalar, basefield and ecc points, we can use the same code base for both executable verifier and verifying circuit.\n\n# 2. Proof Aggregation Approach\n\n## 2.1. Top level sketch:\nSuppose that verifying a single proof of a target circuit $C$ is split into three phases:\n1. Query Constructing (read from transcript)\n2. Construct schema.\n3. Evaluate the multiopen result of $W_x$ and $W_e$ such that\n  $$(W_x[i], x) = (W_e[i], 1)$$\n4. Check the above pair.\n\nThen by the linearity of pairing, we can construct random linear combinition of multiple $W_x[i]$ $W_e[i]$ and check that:\n  $$(\\sum_i \\lambda^{i-1}W_x[i], x) = (\\sum_i \\lambda^{i-1}W_e[i], 1)$$\n\nMotivated by the above thoughts, we split the process of verifying into 4 stages\n* 1. Argument Preparing\n* 2. Query Constructing (read from transcript) for multiple proofs\n* 3. Construct schema from queries of multiple proofs\n* 4. Construct the schema using the linear combination of the schemas from multiple proofs.\n* 4. Evaluate the multiopen result of the schema.\n\nMore precisely the top level pseudo code might looks like the following\n\n```\npub fn verify_aggregation_proofs_in_chip\u003c\n    E: MultiMillerLoop,\n    A: ArithEccChip\u003c\n        Point = E::G1Affine,\n        Scalar = \u003cE::G1Affine as CurveAffine\u003e::ScalarExt,\n        Native = \u003cE::G1Affine as CurveAffine\u003e::ScalarExt,\n    \u003e,\n    T: TranscriptRead\u003cA\u003e,\n\u003e(\n    ctx: \u0026mut A::Context,\n    nchip: \u0026A::NativeChip,\n    schip: \u0026A::ScalarChip,\n    pchip: \u0026A,\n    mut circuits: Vec\u003cCircuitProof\u003cE, A, T\u003e\u003e,\n    transcript: \u0026mut T,\n) -\u003e Result\u003c\n    (\n        A::AssignedPoint, // w_x\n        A::AssignedPoint, // w_g\n        Vec\u003cA::AssignedScalar\u003e, // plain assigned instance\n        Vec\u003cVec\u003cA::AssignedPoint\u003e\u003e, // advice commitments\n    ),\n    A::Error\u003e\n  {\n    let mut plain_assigned_instances = vec![];\n    let multiopen_proofs: Vec\u003cVec\u003c(MultiOpenProof\u003cA\u003e, Vec\u003cA::AssignedPoint\u003e)\u003e\u003e = circuits\n        .iter_mut()\n        .map(|circuit_proof| {\n            let r = circuit_proof\n                .proofs\n                .iter_mut()\n                .map(|proof| {\n\n                    ...\n\n                    let (assigned_instances, assigned_instance_commitments) =\n                        assign_instance_commitment(\n                            ctx,\n                            schip,\n                            pchip,\n                            \u0026instances2[..],\n                            circuit_proof.vk,\n                            circuit_proof.params,\n                        )?;\n\n                    for assigned_instance in assigned_instances {\n                        plain_assigned_instances.push(assigned_instance)\n                    }\n\n                    let (p, c) = verify_single_proof_no_eval()?;\n\n                    Ok((p,c))\n                })\n                .collect();\n\n            /* update aggregation challenge */\n            for p in circuit_proof.proofs.iter_mut() {\n                let scalar = p.transcript.squeeze_challenge_scalar(ctx, nchip, schip)?;\n                transcript.common_scalar(ctx, nchip, schip, \u0026scalar)?;\n            }\n\n            return r;\n        })\n        .collect()?;\n\n\n    let aggregation_challenge = transcript.squeeze_challenge_scalar(ctx, nchip, schip)?;\n\n    let mut acc: Option\u003cMultiOpenProof\u003cA\u003e\u003e = None;\n    let mut commits: Vec\u003cVec\u003cA::AssignedPoint\u003e\u003e = vec![];\n    for (proof, c) in proofs.into_iter() {\n        acc = match acc {\n            None =\u003e Some(proof),\n            Some(acc) =\u003e {\n                Some(MultiOpenProof {\n                    w_x: acc.w_x * scalar!(aggregation_challenge) + proof.w_x,\n                    w_g: acc.w_g * scalar!(aggregation_challenge) + proof.w_g,\n                })\n            }\n        };\n        commits.push(c)\n    }\n    let aggregated_proof = acc.unwrap();\n\n    evaluate_multiopen_proof::\u003cE, A, T\u003e(ctx, schip, pchip, aggregated_proof)\n        .map(|pair| (pair.0, pair.1, plain_assigned_instances, commits))\n\n```\n## 2.2. Calculating the Queries:\nIngredients of queries are decoded from transcripts. We implement the following trait for each context of schema:\n\n### Encode Trait\n```\npub trait Encode\u003cA: ArithEccChip\u003e {\n    fn encode_point(\n        ctx: \u0026mut A::Context,\n        nchip: \u0026A::NativeChip,\n        schip: \u0026A::ScalarChip,\n        pchip: \u0026A,\n        v: \u0026A::AssignedPoint,\n    ) -\u003e Result\u003cVec\u003cA::AssignedNative\u003e, A::Error\u003e;\n    fn encode_scalar(\n        ctx: \u0026mut A::Context,\n        nchip: \u0026A::NativeChip,\n        schip: \u0026A::ScalarChip,\n        v: \u0026A::AssignedScalar,\n    ) -\u003e Result\u003cVec\u003cA::AssignedNative\u003e, A::Error\u003e;\n    fn decode_scalar(\n        ctx: \u0026mut A::Context,\n        nchip: \u0026A::NativeChip,\n        schip: \u0026A::ScalarChip,\n        v: \u0026[A::AssignedNative],\n    ) -\u003e Result\u003cA::AssignedScalar, A::Error\u003e;\n}\n```\n\n### Transcript Read Trait\n```\npub trait TranscriptRead\u003cA: ArithEccChip\u003e {\n    fn read_point(\n        \u0026mut self,\n        ctx: \u0026mut A::Context,\n        nchip: \u0026A::NativeChip,\n        schip: \u0026A::ScalarChip,\n        pchip: \u0026A,\n    ) -\u003e Result\u003cA::AssignedPoint, A::Error\u003e;\n    fn read_scalar(\n        \u0026mut self,\n        ctx: \u0026mut A::Context,\n        nchip: \u0026A::NativeChip,\n        schip: \u0026A::ScalarChip,\n    ) -\u003e Result\u003cA::AssignedScalar, A::Error\u003e;\n\n    fn read_constant_point(\n        \u0026mut self,\n        ctx: \u0026mut A::Context,\n        nchip: \u0026A::NativeChip,\n        schip: \u0026A::ScalarChip,\n        pchip: \u0026A,\n    ) -\u003e Result\u003cA::AssignedPoint, A::Error\u003e;\n    fn read_constant_scalar(\n        \u0026mut self,\n        ctx: \u0026mut A::Context,\n        nchip: \u0026A::NativeChip,\n        schip: \u0026A::ScalarChip,\n    ) -\u003e Result\u003cA::AssignedScalar, A::Error\u003e;\n\n    fn common_point(\n        \u0026mut self,\n        ctx: \u0026mut A::Context,\n        nchip: \u0026A::NativeChip,\n        schip: \u0026A::ScalarChip,\n        pchip: \u0026A,\n        p: \u0026A::AssignedPoint,\n    ) -\u003e Result\u003c(), A::Error\u003e;\n    fn common_scalar(\n        \u0026mut self,\n        ctx: \u0026mut A::Context,\n        nchip: \u0026A::NativeChip,\n        schip: \u0026A::ScalarChip,\n        s: \u0026A::AssignedScalar,\n    ) -\u003e Result\u003c(), A::Error\u003e;\n\n    fn squeeze_challenge_scalar(\n        \u0026mut self,\n        ctx: \u0026mut A::Context,\n        nchip: \u0026A::NativeChip,\n        schip: \u0026A::ScalarChip,\n    ) -\u003e Result\u003cA::AssignedScalar, A::Error\u003e;\n}\n\n```\n## 2.3 Construct schemas\nSchema is construct simply construct the multi-exp ast for queries:\n```\n    pub fn batch_multi_open_proofs(\n        \u0026self,\n        ctx: \u0026mut A::Context,\n        schip: \u0026A::ScalarChip,\n    ) -\u003e Result\u003cMultiOpenProof\u003cA\u003e, A::Error\u003e {\n        let proofs = self.get_point_schemas(ctx, schip)?;\n\n        let mut w_x = None;\n        let mut w_g = None;\n\n        for (i, p) in proofs.into_iter().enumerate() {\n            let s = \u0026p.s;\n            let w = CommitQuery {\n                key: format!(\"{}_w{}\", self.key, i),\n                commitment: Some(p.w.clone()),\n                eval: None,\n            };\n            w_x = w_x.map_or(Some(commit!(w)), |w_x| {\n                Some(scalar!(self.u) * w_x + commit!(w))\n            });\n\n            w_g = w_g.map_or(Some(scalar!(p.point) * commit!(w) + s.clone()), |w_g| {\n                Some(scalar!(self.u) * w_g + scalar!(p.point) * commit!(w) + s.clone())\n            });\n        }\n\n        Ok(MultiOpenProof {\n            w_x: w_x.unwrap(),\n            w_g: w_g.unwrap(),\n        })\n    }\n```\nRecall that at this stage no real calculation was performed but only schema (ast of mult-exp) is constructed.\n\n## Evaluate multi-exp:\nAfter multi-exp for all proofs are constructed, we construct the final ast for multi-exp as follows:\n```\nfor (proof, c) in proofs.into_iter() {\n    acc = match acc {\n        None =\u003e Some(proof),\n        Some(acc) =\u003e {\n            Some(MultiOpenProof {\n                w_x: acc.w_x * scalar!(aggregation_challenge) + proof.w_x,\n                w_g: acc.w_g * scalar!(aggregation_challenge) + proof.w_g,\n            })\n        }\n    };\n    commits.push(c)\n}\nacc.unwrap();\n```\nAnd eventually, we can start calculating the final result by\n```\nevaluate_multiopen_proof::\u003cE, A, T\u003e(ctx, schip, pchip, aggregated_proof)\n    .map(|pair| (pair.0, pair.1, plain_assigned_instances, commits))\n```\n\n# Circuit structure\n\n## Base Arith Gate\n```\n#[derive(Clone, Debug)]\npub struct BaseGateConfig\u003cconst VAR_COLUMNS: usize, const MUL_COLUMNS: usize\u003e {\n    pub base: [Column\u003cAdvice\u003e; VAR_COLUMNS],\n    pub coeff: [Column\u003cFixed\u003e; VAR_COLUMNS],\n    pub mul_coeff: [Column\u003cFixed\u003e; MUL_COLUMNS],\n    pub next_coeff: Column\u003cFixed\u003e,\n    pub constant: Column\u003cFixed\u003e,\n}\n```\n## Transcript Chip\n## Base field Arith Chip\n## Ecc Arith Chip\n\n# Furture Work\n\n1. Wider arith base gate\n2. Multi-layer aggregator\n\n# License\n\nLicensed under either of\n\n- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)\n- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)\n\nat your option.\n\n# Appendix\n## Fp over fr materials\n1. [Multiexp in TurboPlonk](https://docs.zkproof.org/pages/standards/accepted-workshop3/proposal-turbo_plonk.pdf)\n2. [Aztec emulated field and group operations](https://hackmd.io/@arielg/B13JoihA8)\n3. [Implementing ECC addition in orchard using Halo2 (Zcash)](https://zcash.github.io/orchard/design/circuit/gadgets/ecc/addition.html)\n4. [Implementing Fp arithmetic in an Fq circuit (Zcash, R1CS)](https://github.com/zcash/zcash/issues/4093)\n5. [Xjsnark paper for Long Integer Arithmetic (R1CS)](https://akosba.github.io/papers/xjsnark.pdf)\n\n## Query layout:\n```graphviz\ndigraph hierarchy {\nrankdir=\"LR\"\nnodesep=1.0 // increases the separation between nodes\nnode[color=Black,fontname=Courier,shape=box] //All nodes will this shape and colour\nedge [color=Black] //All the lines look like this\nQueries-\u003e{\n    InstanceQueries\n    AdviceQueries\n    FixedQueries\n    PermutationQueries\n    LookupQueries\n    LocalPermutationQueries\n    VanishQueries\n    }  // Put them on the same level\nVanishQueries[color = red]\nAdviceQueries-\u003e {\n    AdviceCommitments\n    AdviceEvals\n    AdviceEvaluationPoints\n}\nInstanceQueries-\u003e {\n    InstanceCommitments\n    InstanceEvals\n    InstanceEvaluationPoints\n}\nFixedQueries-\u003e {\n    FixedEvals\n    FixedCommitments\n    FixedEvaluationPoints\n}\nLocalPermutationQueries -\u003e {\n    LocalPermutationEval\n    LocalPermutationCommitment\n    LocalPermutationEvaluationPoints\n}\nVanishQueries -\u003e {\n    VanishExpressions\n    VanishCommitment\n    VanishEvaluationPoints\n}\n\nPermutationQueries -\u003e {\n    PermutationProductCommitments\n    PermutationProductEvals\n    PermutationEvaluationPoints\n}\n\nLookupQueries -\u003e {\n    LookupCommitment\n    LookupEval\n    LookupEvaluationPoints\n}\n\nLocalPermutationCommitment -\u003e Setup\nFixedCommitments -\u003e Setup\nInstanceCommitments -\u003e InputBeforeXChallenge\nAdviceCommitments -\u003e InputBeforeXChallenge\n\nVanishCommitment -\u003e InputBeforeXChallenge\nInstanceEvals -\u003e InputAfterXChallenge\nAdviceEvals -\u003e InputAfterXChallenge\nFixedEvals -\u003e InputAfterXChallenge\nLocalPermutationEval -\u003e InputBeforeXChallenge\nLocalPermutationCommitment-\u003e InputBeforeXChallenge\nPermutationProductCommitments -\u003e InputBeforeXChallenge\nPermutationProductEvals -\u003e InputAfterXChallenge\nLocalPermutationEvaluationPoints-\u003e xChallenge\nPermutationEvaluationPoints -\u003e xChallenge\nFixedEvaluationPoints-\u003e xChallenge\nInstanceEvaluationPoints -\u003e xChallenge\nAdviceEvaluationPoints -\u003e xChallenge\nVanishEvaluationPoints -\u003e xChallenge\nLookupEvaluationPoints -\u003e xChallenge\nVanishExpressions -\u003e xChallenge\nxChallenge-\u003e InputBeforeXChallenge\nLookupCommitment -\u003e InputBeforeXChallenge\nLookupEval -\u003e InputAfterXChallenge\n\n\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscroll-tech%2Fhalo2-snark-aggregator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscroll-tech%2Fhalo2-snark-aggregator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscroll-tech%2Fhalo2-snark-aggregator/lists"}