diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b1702c60f7..c6cd03bdc7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,12 +8,10 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - # We don't need to test across multiple platforms yet - # os: [ubuntu-latest, windows-latest, macOS-latest] - os: [ubuntu-latest] + os: [ubuntu-latest, windows-latest, macOS-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: override: false @@ -33,7 +31,7 @@ jobs: - wasm32-wasi steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: override: false @@ -50,7 +48,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: override: false @@ -66,7 +64,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Use stable for this to ensure that cargo-tarpaulin can be built. - uses: actions-rs/toolchain@v1 with: @@ -82,16 +80,14 @@ jobs: command: tarpaulin args: --all-features --timeout 600 --out Xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 - with: - token: ${{secrets.CODECOV_TOKEN}} + uses: codecov/codecov-action@v3.1.0 doc-links: name: Intra-doc links runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: override: false @@ -113,7 +109,7 @@ jobs: timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: override: false diff --git a/.github/workflows/lints-stable.yml b/.github/workflows/lints-stable.yml index ffeb79375f..7666dae1df 100644 --- a/.github/workflows/lints-stable.yml +++ b/.github/workflows/lints-stable.yml @@ -5,12 +5,12 @@ on: pull_request jobs: clippy: - name: Clippy (1.51.0) + name: Clippy (1.56.1) timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: components: clippy @@ -18,6 +18,6 @@ jobs: - name: Run clippy uses: actions-rs/clippy-check@v1 with: - name: Clippy (1.51.0) + name: Clippy (1.56.1) token: ${{ secrets.GITHUB_TOKEN }} args: --all-features --all-targets -- -D warnings diff --git a/.gitignore b/.gitignore index 1bd93e286e..f2af733bf1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ Cargo.lock .vscode **/*.html +.DS_Store diff --git a/COPYING b/COPYING deleted file mode 100644 index 6bec3985f5..0000000000 --- a/COPYING +++ /dev/null @@ -1,35 +0,0 @@ -Copyright 2020-2021 The Electric Coin Company - -This package ("Original Work") is licensed under the terms of the Bootstrap Open -Source License, version 1.0, or at your option, any later version ("BOSL"). See -the file ./LICENSE-BOSL for the terms of the Bootstrap Open Source Licence, -version 1.0. - -Only if this Original Work is included as part of the distribution of one of the -following projects ("the Project"): - -- The Zcash projects published by the Electric Coin Company, -- The Zebra project published by the Zcash Foundation, - -then License is granted to use this package under the BOSL as modified by the -following clarification and special exception. This exception applies only to -the Original Work when linked or combined with the Project and not to the -Original Work when linked, combined, or included in or with any other software -or project or on a standalone basis. - - Under the terms of the BOSL, linking or combining this Original Work with - the Project creates a Derivative Work based upon the Original Work and the - terms of the BOSL thus apply to both the Original Work and that Derivative - Work. As a special exception to the BOSL, and to allow this Original Work to - be linked and combined with the Project without having to apply the BOSL to - the other portions of the Project, you are granted permission to link or - combine this Original Work with the Project and to copy and distribute the - resulting work ("Resulting Work") under the open source license applicable - to the Project ("Project License"), provided that any portions of this - Original Work included in the Resulting Work remain subject to the BOSL. For - clarity, you may continue to treat all other portions of the Project under - the Project License, provided that you comply with the BOSL with respect to - the Original Work. If you modify this Original Work, your version of the - Original Work must remain under the BOSL. You may also extend this exception - to your version, but you are not obligated to do so. If you do not wish to - do so, delete this exception statement from your version. diff --git a/COPYING.md b/COPYING.md new file mode 100644 index 0000000000..4f1b0b77b4 --- /dev/null +++ b/COPYING.md @@ -0,0 +1,16 @@ +# License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +# Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. + diff --git a/README.md b/README.md index 5fe417bca3..69167e0716 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ # halo2 [![Crates.io](https://img.shields.io/crates/v/halo2.svg)](https://crates.io/crates/halo2) # -**IMPORTANT**: This library is in beta, and should not be used in production software. - ## [Documentation](https://docs.rs/halo2) ## Minimum Supported Rust Version -Requires Rust **1.51** or higher. +Requires Rust **1.56.1** or higher. Minimum supported Rust version can be changed in the future, but it will be done with a minor version bump. @@ -18,14 +16,17 @@ The `RAYON_NUM_THREADS` environment variable can be used to set the number of th ## License -Copyright 2020-2021 The Electric Coin Company. +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. -You may use this package under the Bootstrap Open Source Licence, version 1.0, -or at your option, any later version. See the file [`COPYING`](COPYING) for -more details, and [`LICENSE-BOSL`](LICENSE-BOSL) for the terms of the Bootstrap -Open Source Licence, version 1.0. +### Contribution -The purpose of the BOSL is to allow commercial improvements to the package -while ensuring that all improvements are open source. See -[here](https://electriccoin.co/blog/introducing-tgppl-a-radically-new-type-of-open-source-license/) -for why the BOSL exists. +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/book/macros.txt b/book/macros.txt index 6ec36ee757..22eab46299 100644 --- a/book/macros.txt +++ b/book/macros.txt @@ -20,6 +20,7 @@ # Circuit constraint helper methods \BoolCheck:{\texttt{bool\_check}({#1})} +\Ternary:{\texttt{ternary}({{#1}, {#2}, {#3}})} \RangeCheck:{\texttt{range\_check}({#1, #2})} \ShortLookupRangeCheck:{\texttt{short\_lookup\_range\_check}({#1})} @@ -51,7 +52,7 @@ \bottom:{\perp} \alg:{#1_\textnormal{alg}} \zero:{\mathcal{O}} -\dlrel:{\mathsf{dl-rel}} +\dlrel:{\textsf{dl-rel}} \game:{\mathsf{G}} \innerprod:{\langle{#1},{#2}\rangle} \dlgame:{\mathsf{G}^\dlrel_{\group,n}} @@ -61,4 +62,15 @@ \halo:{\textsf{Halo}} \lo:{\textnormal{lo}} \hi:{\textnormal{hi}} -\protocol:{\halo} \ No newline at end of file +\protocol:{\halo} +\extractwitness:{\textnormal{ExtractWitness}} +\pfail:{p_\textnormal{fail}} +\repr:\{\kern-0.1em {#1} \kern-0.1em\}^{#2} +\rep:{\repr{#1}{}} +\repv:{\repr{#1}{\mathbf{#2}}_{#3}} +\dlreladv:{\mathcal{H}} +\mr:{\mathcal{M}^{#1}_{#2}({#3})} +\mv:{\mr{\mathbf{#1}}{#2}{#3}} +\m:{\mr{#1}{}{#2}} +\z:{\mathcal{Z}_{#1}({#2}, {#3})} +\trprefix:{{#1}|_{#2}} diff --git a/book/src/IDENTIFIERS.json b/book/src/IDENTIFIERS.json new file mode 100644 index 0000000000..2508a78a0f --- /dev/null +++ b/book/src/IDENTIFIERS.json @@ -0,0 +1,25 @@ +{ + "decompose-combined-lookup": "design/gadgets/decomposition.html#combined-lookup-expression", + "decompose-short-lookup": "design/gadgets/decomposition.html#short-range-check", + "decompose-short-range": "design/gadgets/decomposition.html#short-range-decomposition", + "ecc-complete-addition": "design/gadgets/ecc/addition.html#complete-addition-constraints", + "ecc-incomplete-addition": "design/gadgets/ecc/addition.html#incomplete-addition-constraints", + "ecc-fixed-mul-base-canonicity": "design/gadgets/ecc/fixed-base-scalar-mul.html#base-field-element", + "ecc-fixed-mul-coordinates": "design/gadgets/ecc/fixed-base-scalar-mul.html#constrain-coordinates", + "ecc-fixed-mul-full-word": "design/gadgets/ecc/fixed-base-scalar-mul.html#full-width-scalar", + "ecc-fixed-mul-load-base": "design/gadgets/ecc/fixed-base-scalar-mul.html#load-fixed-base", + "ecc-fixed-mul-short-msb": "design/gadgets/ecc/fixed-base-scalar-mul.html#constrain-short-signed-msb", + "ecc-fixed-mul-short-conditional-neg": "design/gadgets/ecc/fixed-base-scalar-mul.html#constrain-short-signed-conditional-neg", + "ecc-var-mul-complete-gate": "design/gadgets/ecc/var-base-scalar-mul.html#complete-gate", + "ecc-var-mul-incomplete-first-row": "design/gadgets/ecc/var-base-scalar-mul.html#incomplete-first-row-gate", + "ecc-var-mul-incomplete-last-row": "design/gadgets/ecc/var-base-scalar-mul.html#incomplete-last-row-gate", + "ecc-var-mul-incomplete-main-loop": "design/gadgets/ecc/var-base-scalar-mul.html#incomplete-main-loop-gate", + "ecc-var-mul-lsb-gate": "design/gadgets/ecc/var-base-scalar-mul.html#lsb-gate", + "ecc-var-mul-overflow": "design/gadgets/ecc/var-base-scalar-mul.html#overflow-check-constraints", + "ecc-var-mul-witness-scalar": "design/gadgets/ecc/var-base-scalar-mul.html#witness-scalar", + "ecc-witness-point": "design/gadgets/ecc/witnessing-points.html#points-including-the-identity", + "ecc-witness-non-identity-point": "design/gadgets/ecc/witnessing-points.html#non-identity-points", + "sinsemilla-constraints": "design/gadgets/sinsemilla.html#optimized-sinsemilla-gate", + "sinsemilla-merkle-crh-bit-lengths": "design/gadgets/sinsemilla/merkle-crh.html#bit-length-constraints", + "sinsemilla-merkle-crh-decomposition": "design/gadgets/sinsemilla/merkle-crh.html#decomposition-constraints" +} \ No newline at end of file diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index ed1b2b262c..259af2f141 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -25,8 +25,10 @@ - [Implementation](design/implementation.md) - [Proofs](design/implementation/proofs.md) - [Fields](design/implementation/fields.md) + - [Selector combining](design/implementation/selector-combining.md) - [Gadgets](design/gadgets.md) - [Elliptic curve cryptography](design/gadgets/ecc.md) + - [Witnessing points](design/gadgets/ecc/witnessing-points.md) - [Incomplete and complete addition](design/gadgets/ecc/addition.md) - [Fixed-base scalar multiplication](design/gadgets/ecc/fixed-base-scalar-mul.md) - [Variable-base scalar multiplication](design/gadgets/ecc/var-base-scalar-mul.md) diff --git a/book/src/background/curves.md b/book/src/background/curves.md index 36f3e409d2..2600127ddf 100644 --- a/book/src/background/curves.md +++ b/book/src/background/curves.md @@ -155,10 +155,10 @@ when adding two distinct points. ### Point addition We now add two points with distinct $x$-coordinates, $P = (x_0, y_0)$ and $Q = (x_1, y_1),$ where $x_0 \neq x_1,$ to obtain $R = P + Q = (x_2, y_2).$ The line $\overline{PQ}$ has slope -$$\lambda = frac{y_1 - y_0}{x_1 - x_0} \implies y - y_0 = \lambda \cdot (x - x_0).$$ +$$\lambda = \frac{y_1 - y_0}{x_1 - x_0} \implies y - y_0 = \lambda \cdot (x - x_0).$$ Using the expression for $\overline{PQ}$, we compute $y$-coordinate $-y_2$ of $-R$ as: -$$-y_2 - y_0 = \lambda \cdot (x_2 - x_0) \implies \boxed{y_2 = (x_0 - x_2) - y_0}.$$ +$$-y_2 - y_0 = \lambda \cdot (x_2 - x_0) \implies \boxed{y_2 =\lambda (x_0 - x_2) - y_0}.$$ Plugging the expression for $\overline{PQ}$ into the curve equation $y^2 = x^3 + b$ yields $$ @@ -193,7 +193,7 @@ Important notes: Imagine that $\mathbb{F}_p$ has a primitive cube root of unity, or in other words that $3 | p - 1$ and so an element $\zeta_p$ generates a $3$-order multiplicative subgroup. Notice that a point $(x, y)$ on our example elliptic curve $y^2 = x^3 + b$ has two cousin -points: $(\zeta_p x, \zeta_p^2 x)$, because the computation $x^3$ effectively kills the +points: $(\zeta_p x,y), (\zeta_p^2 x,y)$, because the computation $x^3$ effectively kills the $\zeta$ component of the $x$-coordinate. Applying the map $(x, y) \mapsto (\zeta_p x, y)$ is an application of an endomorphism over the curve. The exact mechanics involved are complicated, but when the curve has a prime $q$ number of points (and thus a prime diff --git a/book/src/background/fields.md b/book/src/background/fields.md index fa03e36871..325637010a 100644 --- a/book/src/background/fields.md +++ b/book/src/background/fields.md @@ -248,7 +248,7 @@ odd, and so half of all elements are squares. In order to compute the square root, we can first raise the element $a = \alpha^i \cdot \beta^j$ to the power $t$ to "kill" the $t$-order component, giving -$$a^t = \alpha^{it \pmod 2^k} \cdot \beta^{jt \pmod t} = \alpha^{it \pmod 2^k}$$ +$$a^t = \alpha^{it \pmod {2^k}} \cdot \beta^{jt \pmod t} = \alpha^{it \pmod {2^k}}$$ and then raise this result to the power $t^{-1} \pmod{2^k}$ to undo the effect of the original exponentiation on the $2^k$-order component: diff --git a/book/src/background/groups.md b/book/src/background/groups.md index 12b622bfdc..da8cbc385e 100644 --- a/book/src/background/groups.md +++ b/book/src/background/groups.md @@ -36,14 +36,14 @@ Assuming the discrete log assumption holds, Pedersen commitments are also perfec and computationally binding: * **hiding**: the adversary chooses messages $m_0, m_1.$ The committer commits to one of - these messages $c = \text{Commit}(m_b;r), b \in \{0,1\}.$ Given $c,$ the probability of + these messages $c = \text{Commit}(m_b,r), b \in \{0,1\}.$ Given $c,$ the probability of the adversary guessing the correct $b$ is no more than $\frac{1}{2}$. * **binding**: the adversary cannot pick two different messages $m_0 \neq m_1,$ and randomness $r_0, r_1,$ such that $\text{Commit}(m_0,r_0) = \text{Commit}(m_1,r_1).$ ### Vector Pedersen commitment We can use a variant of the Pedersen commitment scheme to commit to multiple messages at -once, $\mathbf{m} = (m_1, \cdots, m_n)$. This time, we'll have to sample a corresponding +once, $\mathbf{m} = (m_0, \cdots, m_{n-1})$. This time, we'll have to sample a corresponding number of random public generators $\mathbf{G} = (G_0, \cdots, G_{n-1}),$ along with a single random generator $H$ as before (for use in hiding). Then, our commitment scheme is: @@ -57,10 +57,10 @@ $$ > TODO: is this positionally binding? -## Diffie--Hellman +## Diffie–Hellman -An example of a protocol that uses cryptographic groups is Diffie--Hellman key agreement -[[DH1976]]. The Diffie--Hellman protocol is a method for two users, Alice and Bob, to +An example of a protocol that uses cryptographic groups is Diffie–Hellman key agreement +[[DH1976]]. The Diffie–Hellman protocol is a method for two users, Alice and Bob, to generate a shared private key. It proceeds as follows: 1. Alice and Bob publicly agree on two prime numbers, $p$ and $G,$ where $p$ is large and @@ -83,7 +83,7 @@ $g, p, A = [a]G,$ and $B = [b]G$: in other words, they would need to either get discrete logarithm $a$ from $A = [a]G$ or $b$ from $B = [b]G,$ which we assume to be computationally infeasible in $\mathbb{F}_p^\times.$ -More generally, protocols that use similar ideas to Diffie--Hellman are used throughout +More generally, protocols that use similar ideas to Diffie–Hellman are used throughout cryptography. One way of instantiating a cryptographic group is as an [elliptic curve](curves.md). Before we go into detail on elliptic curves, we'll describe some algorithms that can be used for any group. diff --git a/book/src/background/pc-ipa.md b/book/src/background/pc-ipa.md index 323c3fce95..63410c8ddb 100644 --- a/book/src/background/pc-ipa.md +++ b/book/src/background/pc-ipa.md @@ -53,7 +53,7 @@ $\mathbf{b}^{(k)} := \mathbf{b}.$ In each round $j = k, k-1, \cdots, 1$: $$ \begin{aligned} L_j &= \langle\mathbf{a_{lo}^{(j)}}, \mathbf{G_{hi}^{(j)}}\rangle + [l_j]H + [\langle\mathbf{a_{lo}^{(j)}}, \mathbf{b_{hi}^{(j)}}\rangle] U\\ -R_j &= \langle\mathbf{a_{hi}^{(j)}}, \mathbf{G_{lo}^{(j)}}\rangle + [l_j]H + [\langle\mathbf{a_{hi}^{(j)}}, \mathbf{b_{lo}^{(j)}}\rangle] U\\ +R_j &= \langle\mathbf{a_{hi}^{(j)}}, \mathbf{G_{lo}^{(j)}}\rangle + [r_j]H + [\langle\mathbf{a_{hi}^{(j)}}, \mathbf{b_{lo}^{(j)}}\rangle] U\\ \end{aligned} $$ diff --git a/book/src/background/polynomials.md b/book/src/background/polynomials.md index 6ed4a0c16f..7846233724 100644 --- a/book/src/background/polynomials.md +++ b/book/src/background/polynomials.md @@ -178,7 +178,7 @@ most points." Formally, it can be written as follows: > Let $p(x_1, x_2, \cdots, x_n)$ be a nonzero polynomial of $n$ variables with degree $d$. > Let $S$ be a finite set of numbers with at least $d$ elements in it. If we choose random -> $\alpha_1, \alpha_1, \cdots, \alpha_n$ from $S$, +> $\alpha_1, \alpha_2, \cdots, \alpha_n$ from $S$, > $$\text{Pr}[p(\alpha_1, \alpha_2, \cdots, \alpha_n) = 0] \leq \frac{d}{|S|}.$$ In the familiar univariate case $p(X)$, this reduces to saying that a nonzero polynomial @@ -279,7 +279,7 @@ we can reconstruct its coefficient form in the Lagrange basis: $$A(X) = \sum_{i = 0}^{n-1} A(x_i)\mathcal{L_i}(X), $$ -where $X \in \{x_0, x_1,\cdots, x_{1-n}\}.$ +where $X \in \{x_0, x_1,\cdots, x_{n-1}\}.$ ## References [^master-thm]: [Dasgupta, S., Papadimitriou, C. H., & Vazirani, U. V. (2008). "Algorithms" (ch. 2). New York: McGraw-Hill Higher Education.](https://people.eecs.berkeley.edu/~vazirani/algorithms/chap2.pdf) diff --git a/book/src/background/recursion.md b/book/src/background/recursion.md index 30bf19fbf0..4b6ce7430f 100644 --- a/book/src/background/recursion.md +++ b/book/src/background/recursion.md @@ -14,7 +14,7 @@ polynomial with degree $2^k - 1.$ | | | | -------- | -------- | -| | Since $G$ is a commitment, it can be checked in an inner product argument. The verifier circuit witnesses $G$ and brings $G, u_1, \cdots, u_k$ out as public inputs to the proof $\pi.$ The next verifier instance checks $\pi$ using the inner product argument; this includes checking that $G = \text{Commit}(g(X, u_1, \cdots, u_k))$ evaluates at some random point to the expected value for the given challenges $u_1, \cdots, u_k.$ Recall from the [previous section](#Polynomial-commitment-using-inner-product-argument) that this check only requires $\log d$ work.

At the end of checking $\pi$ and $G,$ the circuit is left with a new $G',$ along with the $u_1', \cdots, u_k'$ challenges sampled for the check. To fully accept $\pi$ as valid, we should perform a linear-time computation of $G' = \langle\mathbf{G}, \mathbf{s}'\rangle$. Once again, we delay this computation by witnessing $G'$ and bringing $G, u_1, \cdots, u_k$ out as public inputs to the proof $\pi.$

This goes on from one proof instance to the next, until we are satisfied with the size of our batch of proofs. We finally perform a single linear-time computation, thus deciding the validity of the whole batch. | +| | Since $G$ is a commitment, it can be checked in an inner product argument. The verifier circuit witnesses $G$ and brings $G, u_1, \cdots, u_k$ out as public inputs to the proof $\pi.$ The next verifier instance checks $\pi$ using the inner product argument; this includes checking that $G = \text{Commit}(g(X, u_1, \cdots, u_k))$ evaluates at some random point to the expected value for the given challenges $u_1, \cdots, u_k.$ Recall from the [previous section](#Polynomial-commitment-using-inner-product-argument) that this check only requires $\log d$ work.

At the end of checking $\pi$ and $G,$ the circuit is left with a new $G',$ along with the $u_1', \cdots, u_k'$ challenges sampled for the check. To fully accept $\pi$ as valid, we should perform a linear-time computation of $G' = \langle\mathbf{G}, \mathbf{s}'\rangle$. Once again, we delay this computation by witnessing $G'$ and bringing $G', u_1', \cdots, u_k'$ out as public inputs to the proof $\pi'.$

This goes on from one proof instance to the next, until we are satisfied with the size of our batch of proofs. We finally perform a single linear-time computation, thus deciding the validity of the whole batch. | We recall from the section [Cycles of curves](curves.md#cycles-of-curves) that we can instantiate this protocol over a two-cycle, where a proof produced by one curve is diff --git a/book/src/concepts/arithmetization.md b/book/src/concepts/arithmetization.md index 181f468233..65b25c8274 100644 --- a/book/src/concepts/arithmetization.md +++ b/book/src/concepts/arithmetization.md @@ -19,13 +19,13 @@ A PLONKish circuit depends on a ***configuration***: * A subset of the columns that can participate in equality constraints. -* A ***polynomial degree bound***. +* A ***maximum constraint degree***. * A sequence of ***polynomial constraints***. These are multivariate polynomials over $\mathbb{F}$ that must evaluate to zero *for each row*. The variables in a polynomial constraint may refer to a cell in a given column of the current row, or a given column of another row relative to this one (with wrap-around, i.e. taken modulo $n$). The maximum - degree of each polynomial is given by the polynomial degree bound. + degree of each polynomial is given by the maximum constraint degree. * A sequence of ***lookup arguments*** defined over tuples of ***input expressions*** (which are multivariate polynomials as above) and ***table columns***. diff --git a/book/src/design/gadgets/decomposition.md b/book/src/design/gadgets/decomposition.md index 2a3febeb32..94e2349081 100644 --- a/book/src/design/gadgets/decomposition.md +++ b/book/src/design/gadgets/decomposition.md @@ -61,7 +61,7 @@ $$ \begin{array}{|c|l|} \hline \text{Degree} & \text{Constraint} \\\hline - 2 & q_\mathit{bitshift} \cdot (\alpha' - (\alpha \cdot 2^{K - n})) \\\hline + 2 & q_\mathit{bitshift} \cdot ((\alpha \cdot 2^{K - n}) - \alpha') \\\hline \end{array} $$ diff --git a/book/src/design/gadgets/ecc.md b/book/src/design/gadgets/ecc.md index e69de29bb2..e1025ee98f 100644 --- a/book/src/design/gadgets/ecc.md +++ b/book/src/design/gadgets/ecc.md @@ -0,0 +1,50 @@ +# Elliptic Curves + +## `EccChip` + +`halo2_gadgets` provides a chip that implements `EccInstructions` using 10 advice columns. +The chip is currently restricted to the Pallas curve, but will be extended to support the +[Vesta curve](https://github.com/zcash/halo2/issues/578) in the near future. + +### Chip assumptions + +A non-exhaustive list of assumptions made by `EccChip`: +- $0$ is not an $x$-coordinate of a valid point on the curve. + - Holds for Pallas because $5$ is not square in $\mathbb{F}_q$. +- $0$ is not a $y$-coordinate of a valid point on the curve. + - Holds for Pallas because $-5$ is not a cube in $\mathbb{F}_q$. + +### Layout + +The following table shows how columns are used by the gates for various chip sub-areas: + +- $W$ - witnessing points. +- $AI$ - incomplete point addition. +- $AC$ - complete point addition. +- $MF$ - Fixed-base scalar multiplication. +- $MVI$ - variable-base scalar multiplication, incomplete rounds. +- $MVC$ - variable-base scalar multiplication, complete rounds. +- $MVO$ - variable-base scalar multiplication, overflow check. + +$$ +\begin{array}{|c||c|c|c|c|c|c|c|c|c|c|} +\hline +\text{Sub-area} & a_0 & a_1 & a_2 & a_3 & a_4 & a_5 & a_6 & a_7 & a_8 & a_9 \\\hline +\hline + W & x & y \\\hline +\hline + AI & x_p & y_p & x_q & y_q \\\hline + & & & x_r & y_r \\\hline +\hline + AC & x_p & y_p & x_q & y_q & \lambda & \alpha & \beta & \gamma & \delta & \\\hline + & & & x_r & y_r \\\hline +\hline + MF & x_p & y_p & x_q & y_q & \text{window} & u \\\hline + & & & x_r & y_r \\\hline +\hline +MVI & x_p & y_p & \lambda_2^{lo} & x_A^{hi} & \lambda_1^{hi} & \lambda_2^{hi} & z^{lo} & x_A^{lo} & \lambda_1^{lo} & z^{hi} \\\hline +\hline +MVC & x_p & y_p & x_q & y_q & \lambda & \alpha & \beta & \gamma & \delta & z^{complete} \\\hline + & & & x_r & y_r \\\hline +\end{array} +$$ diff --git a/book/src/design/gadgets/ecc/addition.md b/book/src/design/gadgets/ecc/addition.md index ff24da0d1f..a51d9dc165 100644 --- a/book/src/design/gadgets/ecc/addition.md +++ b/book/src/design/gadgets/ecc/addition.md @@ -9,44 +9,48 @@ derived from section 4.1 of [Hüseyin Hışıl's thesis](https://core.ac.uk/down The formulae from Hışıl's thesis are: - $x_3 = \left(\frac{y_1 - y_2}{x_1 - x_2}\right)^2 - x_1 - x_2$ -- $y_3 = \frac{y_1 - y_2}{x_1 - x_2} \cdot (x_1 - x_3) - y_1$ +- $y_3 = \frac{y_1 - y_2}{x_1 - x_2} \cdot (x_1 - x_3) - y_1.$ -Rename: -- $(x_1, y_1)$ to $(x_q, y_q)$ -- $(x_2, y_2)$ to $(x_p, y_p)$ -- $(x_3, y_3)$ to $(x_r, y_r)$. +Rename $(x_1, y_1)$ to $(x_q, y_q)$, $(x_2, y_2)$ to $(x_p, y_p)$, and $(x_3, y_3)$ to $(x_r, y_r)$, giving -Let $\lambda = \frac{y_q - y_p}{x_q - x_p} = \frac{y_p - y_q}{x_p - x_q}$, which we implement as - -$\lambda \cdot (x_p - x_q) = y_p - y_q$ - -Also, -- $x_r = \lambda^2 - x_q - x_p$ -- $y_r = \lambda \cdot (x_q - x_r) - y_q$ +- $x_r = \left(\frac{y_q - y_p}{x_q - x_p}\right)^2 - x_q - x_p$ +- $y_r = \frac{y_q - y_p}{x_q - x_p} \cdot (x_q - x_r) - y_q$ which is equivalent to -- $x_r + x_q + x_p = \lambda^2$ +- $x_r + x_q + x_p = \left(\frac{y_p - y_q}{x_p - x_q}\right)^2$ +- $y_r + y_q = \frac{y_p - y_q}{x_p - x_q} \cdot (x_q - x_r).$ -Assuming $x_p \neq x_q$, +Assuming $x_p \neq x_q$, we have $ \begin{array}{lrrll} -&&(x_r + x_q + x_p) \cdot (x_p - x_q)^2 &=& \lambda^2 \cdot (x_p - x_q)^2 \\ -&\implies &(x_r + x_q + x_p) \cdot (x_p - x_q)^2 &=& \big(\lambda \cdot (x_p - x_q)\big)^2 \\[1.2ex] +&& x_r + x_q + x_p &=& \left(\frac{y_p - y_q}{x_p - x_q}\right)^2 \\[1.2ex] +&\Longleftrightarrow &(x_r + x_q + x_p) \cdot (x_p - x_q)^2 &=& (y_p - y_q)^2 \\[1ex] +&\Longleftrightarrow &(x_r + x_q + x_p) \cdot (x_p - x_q)^2 - (y_p - y_q)^2 &=& 0 \\[1.5ex] \text{and} \\ -& &y_r &=& \lambda \cdot (x_q - x_r) - y_q \\ -&\implies &y_r + y_q &=& \lambda \cdot (x_q - x_r) \\ -&\implies &(y_r + y_q) \cdot (x_p - x_q) &=& \lambda \cdot (x_p - x_q) \cdot (x_q - x_r) +&&y_r + y_q &=& \frac{y_p - y_q}{x_p - x_q} \cdot (x_q - x_r) \\[0.8ex] +&\Longleftrightarrow &(y_r + y_q) \cdot (x_p - x_q) &=& (y_p - y_q) \cdot (x_q - x_r) \\[1ex] +&\Longleftrightarrow &(y_r + y_q) \cdot (x_p - x_q) - (y_p - y_q) \cdot (x_q - x_r) &=& 0. \end{array} $ -Substituting for $\lambda \cdot (x_p - x_q)$, we get the constraints: +So we get the constraints: - $(x_r + x_q + x_p) \cdot (x_p - x_q)^2 - (y_p - y_q)^2 = 0$ - Note that this constraint is unsatisfiable for $P \;⸭\; (-P)$ (when $P \neq \mathcal{O}$), and so cannot be used with arbitrary inputs. -- $(y_r + y_q) \cdot (x_p - x_q) - (y_p - y_q) \cdot (x_q - x_r) = 0$ +- $(y_r + y_q) \cdot (x_p - x_q) - (y_p - y_q) \cdot (x_q - x_r) = 0.$ +### Constraints + +$$ +\begin{array}{|c|l|} +\hline +\text{Degree} & \text{Constraint} \\\hline +4 & q_\text{add-incomplete} \cdot \left( (x_r + x_q + x_p) \cdot (x_p - x_q)^2 - (y_p - y_q)^2 \right) = 0 \\\hline +3 & q_\text{add-incomplete} \cdot \left( (y_r + y_q) \cdot (x_p - x_q) - (y_p - y_q) \cdot (x_q - x_r) \right) = 0 \\\hline +\end{array} +$$ ## Complete addition @@ -95,10 +99,10 @@ $\hspace{1em} \end{array} $ -### Constraints +### Constraints $$ -\begin{array}{|c|rcl|l|} +\begin{array}{|c|lcl|l|} \hline \text{Degree} & \text{Constraint}\hspace{7em} &&& \text{Meaning} \\\hline 4 & q_\mathit{add} \cdot (x_q - x_p) \cdot ((x_q - x_p) \cdot \lambda - (y_q - y_p)) &=& 0 & x_q \neq x_p \implies \lambda = \frac{y_q - y_p}{x_q - x_p} \\\hline \\[-2.3ex] @@ -199,16 +203,16 @@ $$ & \text{Therefore:} \\ & \hspace{2em} x_p = 0 \implies (x_r, y_r) = (x_q, y_q). \\ & \\ -5.\text{ a)} & (1 - x_q \cdot \beta) \cdot (x_r - x_p) = 0 \\ - \text{ b)} & (1 - x_q \cdot \beta) \cdot (y_r - y_p) = 0 \\ +5.\text{ a)} & (1 - x_q \cdot \gamma) \cdot (x_r - x_p) = 0 \\ + \text{ b)} & (1 - x_q \cdot \gamma) \cdot (y_r - y_p) = 0 \\ & \\ & \begin{aligned} - \text{At least one of } 1 - x_q \cdot \beta &= 0 \\ + \text{At least one of } 1 - x_q \cdot \gamma &= 0 \\ \text{or } x_r - x_p &= 0 \end{aligned} \\ & \text{must be satisfied for constraint (a) to be satisfied.} \\ & \\ - & \text{If } x_q = 0 \text{ then } 1 - x_q \cdot \beta = 0 \text{ has no solutions for } \beta, \\ + & \text{If } x_q = 0 \text{ then } 1 - x_q \cdot \gamma = 0 \text{ has no solutions for } \gamma, \\ & \text{and so it must be that } x_r - x_p = 0. \\ & \\ & \text{Similarly, constraint (b) imposes that if } x_q = 0 \\ @@ -227,7 +231,7 @@ $$ & \text{must be satisfied for constraint (a) to be satisfied,} \\ & \text{and similarly replacing } x_r \text{ by } y_r. \\ & \\ - & \text{If } x_r \neq 0 \text{ or } y_r = 0, \text{ then it must be that } 1 - (x_q - x_p) \cdot \alpha - (y_q + y_p) \cdot \delta = 0. \\ + & \text{If } x_r \neq 0 \text{ or } y_r \neq 0, \text{ then it must be that } 1 - (x_q - x_p) \cdot \alpha - (y_q + y_p) \cdot \delta = 0. \\ & \\ & \text{However, if } x_q = x_p \wedge y_q = -y_p, \text{ then there are no solutions for } \alpha \text { and } \delta. \\ & \\ diff --git a/book/src/design/gadgets/ecc/fixed-base-scalar-mul.md b/book/src/design/gadgets/ecc/fixed-base-scalar-mul.md index 244a955c1b..dbeece496d 100644 --- a/book/src/design/gadgets/ecc/fixed-base-scalar-mul.md +++ b/book/src/design/gadgets/ecc/fixed-base-scalar-mul.md @@ -13,22 +13,15 @@ A $255$-bit scalar from $\mathbb{F}_q$. We decompose a full-width scalar $\alpha $$\alpha = k_0 + k_1 \cdot (2^3)^1 + \cdots + k_{84} \cdot (2^3)^{84}, k_i \in [0..2^3).$$ -The scalar multiplication will be computed correctly for $k_{0..84}$ representing any integer in the range $[0, 2^{255})$. - -$$ -\begin{array}{|c|l|} -\hline -\text{Degree} & \text{Constraint} \\\hline -9 & q_\text{scalar-fixed} \cdot \left(\sum\limits_{i=0}^7{w - i}\right) = 0 \\\hline -\end{array} -$$ +The scalar multiplication will be computed correctly for $k_{0..84}$ representing any +integer in the range $[0, 2^{255})$ - that is, the scalar is allowed to be non-canonical. We range-constrain each $3$-bit word of the scalar decomposition using a polynomial range-check constraint: $$ \begin{array}{|c|l|} \hline \text{Degree} & \text{Constraint} \\\hline -9 & q_\text{decompose-base-field} \cdot \RangeCheck{\text{word}}{2^3} = 0 \\\hline +9 & q_\texttt{mul\_fixed\_full} \cdot \RangeCheck{\text{word}}{2^3} = 0 \\\hline \end{array} $$ where $\RangeCheck{\text{word}}{\texttt{range}} = \text{word} \cdot (1 - \text{word}) \cdots (\texttt{range} - 1 - \text{word}).$ @@ -48,7 +41,7 @@ $$ \hline \text{Degree} & \text{Constraint} \\\hline 5 & q_\text{canon-base-field} \cdot \RangeCheck{\alpha_1}{2^2} = 0 \\\hline -3 & q_\text{canon-base-field} \cdot \RangeCheck{\alpha_2}{2^1} = 0 \\\hline +3 & q_\text{canon-base-field} \cdot \BoolCheck{\alpha_2} = 0 \\\hline 2 & q_\text{canon-base-field} \cdot \left(z_{84} - (\alpha_1 + \alpha_2 \cdot 2^2)\right) = 0 \\\hline \end{array} $$ @@ -74,9 +67,10 @@ $$ \begin{array}{|c|l|l|} \hline \text{Degree} & \text{Constraint} & \text{Comment} \\\hline +2 & q_\text{canon-base-field} \cdot (\alpha_0' - (\alpha_0 + 2^{130} - t_\mathbb{P})) = 0 \\\hline 3 & q_\text{canon-base-field} \cdot \alpha_2 \cdot \alpha_1 = 0 & \alpha_2 = 1 \implies \alpha_1 = 0 \\\hline 3 & q_\text{canon-base-field} \cdot \alpha_2 \cdot \textsf{alpha\_0\_hi\_120} = 0 & \text{Constrain $\alpha_0$ to be a $132$-bit value} \\\hline -4 & q_\text{canon-base-field} \cdot \alpha_2 \cdot k_{43} \cdot (1 - k_{43}) = 0 & \text{Constrain $\alpha_0[130..\!\!=\!\!131]$ to $0$} \\\hline +4 & q_\text{canon-base-field} \cdot \alpha_2 \cdot \BoolCheck{k_{43}} = 0 & \text{Constrain $\alpha_0[130..\!\!=\!\!131]$ to $0$} \\\hline 3 & q_\text{canon-base-field} \cdot \alpha_2 \cdot z_{13}(\texttt{lookup}(\alpha_0', 13)) = 0 & \alpha_2 = 1 \implies 0 \leq \alpha'_0 < 2^{130}\\\hline \end{array} $$ @@ -97,13 +91,13 @@ $\mathsf{v^{old}}$ and $\mathsf{v^{new}}$ are each already constrained to $64$ b Decompose the magnitude $m$ into three-bit windows, and range-constrain each window, using the [short range decomposition](../decomposition.md#short-range-decomposition) gadget in strict mode, with $W = 22, K = 3.$ -We have two additional constraints: + We have two additional constraints: $$ \begin{array}{|c|l|l|} \hline \text{Degree} & \text{Constraint} & \text{Comment} \\\hline -3 & q_\text{scalar-fixed-short} \cdot \BoolCheck{k_{21}} = 0 & \text{The last window must be a single bit.}\\\hline -3 & q_\text{scalar-fixed-short} \cdot \left(s^2 - 1\right) = 0 &\text{The sign must be $1$ or $-1$.}\\\hline +3 & q_\texttt{mul\_fixed\_short} \cdot \BoolCheck{k_{21}} = 0 & \text{The last window must be a single bit.}\\\hline +3 & q_\texttt{mul\_fixed\_short} \cdot \left(s^2 - 1\right) = 0 &\text{The sign must be $1$ or $-1$.}\\\hline \end{array} $$ where $\BoolCheck{x} = x \cdot (1 - x)$. @@ -153,7 +147,7 @@ Given a decomposed scalar $\alpha$ and a fixed base $B$, we compute $[\alpha]B$ > Note: complete addition is required in the final step to correctly map $[0]B$ to a representation of the point at infinity, $(0,0)$; and also to handle a corner case for which the last step is a doubling. -Constraints: + Constraints: $$ \begin{array}{|c|l|} \hline @@ -169,12 +163,13 @@ where $b = 5$ (from the Pallas curve equation). ### Signed short exponent Recall that the signed short exponent is witnessed as a $64-$bit magnitude $m$, and a sign $s \in {1, -1}.$ Using the above algorithm, we compute $P = [m] \mathcal{B}$. Then, to get the final result $P',$ we conditionally negate $P$ using $(x, y) \mapsto (x, s \cdot y)$. -Constraints: + Constraints: $$ \begin{array}{|c|l|} \hline \text{Degree} & \text{Constraint} \\\hline -3 & q_\text{mul-fixed-short} \cdot \left(s \cdot P_y - P'_y\right) = 0 \\\hline +3 & q_\texttt{mul\_fixed\_short} \cdot \left(P'_y - P_y\right) \cdot \left(P'_y + P_y\right) = 0 \\\hline +3 & q_\texttt{mul\_fixed\_short} \cdot \left(s \cdot P'_y - P_y\right) = 0 \\\hline \end{array} $$ diff --git a/book/src/design/gadgets/ecc/var-base-scalar-mul.md b/book/src/design/gadgets/ecc/var-base-scalar-mul.md index 6f912d15a7..84ef5ab3dc 100644 --- a/book/src/design/gadgets/ecc/var-base-scalar-mul.md +++ b/book/src/design/gadgets/ecc/var-base-scalar-mul.md @@ -88,7 +88,7 @@ for i from 2 down to 0 { return (k_0 = 0) ? (Acc + (-T)) : Acc // complete addition ``` -## Constraint program for optimized double-and-add (incomplete addition) +## Constraint program for optimized double-and-add Define a running sum $\mathbf{z_j} = \sum_{i=j}^{n} (\mathbf{k}_{i} \cdot 2^{i-j})$, where $n = 254$ and: $$ @@ -108,13 +108,13 @@ $\begin{array}{l} \hspace{1.5em} x_{P,i} = x_T \\ \hspace{1.5em} y_{P,i} = (2 \mathbf{k}_i - 1) \cdot y_T \hspace{2em}\text{(conditionally negate)} \\ \hspace{1.5em} \lambda_{1,i} \cdot (x_{A,i} - x_{P,i}) = y_{A,i} - y_{P,i} \\ -\hspace{1.5em} \lambda_{1,i}^2 = x_{R,i} + x_{A,i} + x_{P,i} \\ +\hspace{1.5em} x_{R,i} = \lambda_{1,i}^2 - x_{A,i} - x_{P,i} \\ \hspace{1.5em} (\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - x_{R,i}) = 2 y_{\mathsf{A},i} \\ \hspace{1.5em} \lambda_{2,i}^2 = x_{A,i-1} + x_{R,i} + x_{A,i} \\ -\hspace{1.5em} \lambda_{2,i} \cdot (x_{A,i} - x_{A,i-1}) = y_{A,i} + y_{A,i-1}, \\ +\hspace{1.5em} \lambda_{2,i} \cdot (x_{A,i} - x_{A,i-1}) = y_{A,i} + y_{A,i-1}. \\ \end{array}$ -where $x_{R,i} = (\lambda_{1,i}^2 - x_{A,i} - x_T).$ The helper $\BoolCheck{x} = x \cdot (1 - x)$. +The helper $\BoolCheck{x} = x \cdot (1 - x)$. After substitution of $x_{P,i}, y_{P,i}, x_{R,i}, y_{A,i}$, and $y_{A,i-1}$, this becomes: $\begin{array}{l} @@ -154,8 +154,8 @@ Output $(x_{A,0}, y_{A,0}) + B$. (Note that $(0, 0)$ represents $\mathcal{O}$.) +## Incomplete addition -### Circuit design We need six advice columns to witness $(x_T, y_T, \lambda_1, \lambda_2, x_{A,i}, \mathbf{z}_i)$. However, since $(x_T, y_T)$ are the same, we can perform two incomplete additions in a single row, reusing the same $(x_T, y_T)$. We split the scalar bits used in incomplete addition into $hi$ and $lo$ halves and process them in parallel. This means that we effectively have two for loops: - the first, covering the $hi$ half for $i$ from $254$ down to $130$, with a special case at $i = 130$; and - the second, covering the $lo$ half for the remaining $i$ from $129$ down to $4$, with a special case at $i = 4$. @@ -169,7 +169,7 @@ $$ x_T & y_T & \mathbf{z}_{253} & x_{A,253} & \lambda_{1,253} & \lambda_{2,253} & 0 & 1 & 0 & \mathbf{z}_{128} & x_{A,128} & \lambda_{1,128} & \lambda_{2,128} & 0 & 1 & 0 \\\hline \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots \\\hline x_T & y_T & \mathbf{z}_{130} & x_{A,130} & \lambda_{1,130} & \lambda_{2,130} & 0 & 0 & 1 & \mathbf{z}_5 & x_{A,5} & \lambda_{1,5} & \lambda_{2,5} & 0 & 1 & 0 \\\hline - & & & x_{A,129} & y_{A,129} & & & & & \mathbf{z}_4 & x_{A,4} & \lambda_{1,4} & \lambda_{2,4} & 0 & 0 & 1 \\\hline + x_T & y_T & & x_{A,129} & y_{A,129} & & & & & \mathbf{z}_4 & x_{A,4} & \lambda_{1,4} & \lambda_{2,4} & 0 & 0 & 1 \\\hline & & & & & & & & & & x_{A,3} & y_{A,3} & & & & \\\hline \end{array} @@ -177,13 +177,13 @@ $$ For each $hi$ and $lo$ half, we have three sets of gates. Note that $i$ is going from $255..=3$; $i$ is NOT indexing the rows. -#### $q_1 = 1$ +### $q_1 = 1$ This gate is only used on the first row (before the for loop). We check that $\lambda_1, \lambda_2$ are initialized to values consistent with the initial $y_A.$ $$ \begin{array}{|c|l|} \hline \text{Degree} & \text{Constraint} \\\hline -3 & q_1 \cdot \left(y_{A,n}^\text{witnessed} - y_{A,n}\right) = 0 \\\hline +4 & q_1 \cdot \left(y_{A,n}^\text{witnessed} - y_{A,n}\right) = 0 \\\hline \end{array} $$ where @@ -194,7 +194,7 @@ y_{A,n}^\text{witnessed} &\text{ is witnessed.} \end{aligned} $$ -#### $q_2 = 1$ +### $q_2 = 1$ This gate is used on all rows corresponding to the for loop except the last. $$ @@ -205,19 +205,20 @@ $$ 2 & q_2 \cdot \left(y_{T,cur} - y_{T,next}\right) = 0 \\\hline 3 & q_2 \cdot \BoolCheck{\mathbf{k}_i} = 0, \text{ where } \mathbf{k}_i = \mathbf{z}_{i} - 2\mathbf{z}_{i+1} \\\hline 4 & q_2 \cdot \left(\lambda_{1,i} \cdot (x_{A,i} - x_{T,i}) - y_{A,i} + (2\mathbf{k}_i - 1) \cdot y_{T,i}\right) = 0 \\\hline -3 & q_2 \cdot \left(\lambda_{2,i}^2 - x_{A,i-1} - \lambda_{1,i}^2 + x_{T,i}\right) = 0 \\\hline +3 & q_2 \cdot \left(\lambda_{2,i}^2 - x_{A,i-1} - x_{R,i} - x_{A,i}\right) = 0 \\\hline 3 & q_2 \cdot \left(\lambda_{2,i} \cdot (x_{A,i} - x_{A,i-1}) - y_{A,i} - y_{A,i-1}\right) = 0 \\\hline \end{array} $$ where $$ \begin{aligned} +x_{R,i} &= \lambda_{1,i}^2 - x_{A,i} - x_T, \\ y_{A,i} &= \frac{(\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - (\lambda_{1,i}^2 - x_{A,i} - x_T))}{2}, \\ y_{A,i-1} &= \frac{(\lambda_{1,i-1} + \lambda_{2,i-1}) \cdot (x_{A,i-1} - (\lambda_{1,i-1}^2 - x_{A,i-1} - x_T))}{2}, \\ \end{aligned} $$ -#### $q_3 = 1$ +### $q_3 = 1$ This gate is used on the final iteration of the for loop, handling the special case where we check that the output $y_A$ has been witnessed correctly. $$ \begin{array}{|c|l|} @@ -225,18 +226,80 @@ $$ \text{Degree} & \text{Constraint} \\\hline 3 & q_3 \cdot \BoolCheck{\mathbf{k}_i} = 0, \text{ where } \mathbf{k}_i = \mathbf{z}_{i} - 2\mathbf{z}_{i+1} \\\hline 4 & q_3 \cdot \left(\lambda_{1,i} \cdot (x_{A,i} - x_{T,i}) - y_{A,i} + (2\mathbf{k}_i - 1) \cdot y_{T,i}\right) = 0 \\\hline -3 & q_3 \cdot \left(\lambda_{2,i}^2 - x_{A,i-1} - \lambda_{1,i}^2 + x_{T,i}\right) = 0 \\\hline +3 & q_3 \cdot \left(\lambda_{2,i}^2 - x_{A,i-1} - x_{R,i} - x_{A,i}\right) = 0 \\\hline 3 & q_3 \cdot \left(\lambda_{2,i} \cdot (x_{A,i} - x_{A,i-1}) - y_{A,i} - y_{A,i-1}^\text{witnessed}\right) = 0 \\\hline \end{array} $$ where $$ \begin{aligned} +x_{R,i} &= \lambda_{1,i}^2 - x_{A,i} - x_T, \\ y_{A,i} &= \frac{(\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - (\lambda_{1,i}^2 - x_{A,i} - x_T))}{2},\\ y_{A,i-1}^\text{witnessed} &\text{ is witnessed.} \end{aligned} $$ +## Complete addition + +We reuse the [complete addition](addition.md#complete-addition) constraints to implement +the final $c$ rounds of double-and-add. This requires two rows per round because we need +9 advice columns for each complete addition. In the 10th advice column we stash the other +cells that we need to correctly implement the double-and-add: + +- The base $y$ coordinate, so we can conditionally negate it as input to one of the + complete additions. +- The running sum, which we constrain over two rows instead of sequentially. + +### Layout + +$$ +\begin{array}{|c|c|c|c|c|c|c|c|c|c|c|} +a_0 & a_1 & a_2 & a_3 & a_4 & a_5 & a_6 & a_7 & a_8 & a_9 & q_\texttt{mul\_decompose\_var} \\\hline +x_T & y_p & x_A & y_A & \lambda_1 & \alpha_1 & \beta_1 & \gamma_1 & \delta_1 & z_{i+1} & 0 \\\hline +x_A & y_A & x_q & y_q & \lambda_2 & \alpha_2 & \beta_2 & \gamma_2 & \delta_2 & y_T & 1 \\\hline + & & x_r & y_r & & & & & & z_i & 0 \\\hline +\end{array} +$$ + +### Constraints + +In addition to the complete addition constraints, we define the following gate: + +$$ +\begin{array}{|c|l|} +\hline +\text{Degree} & \text{Constraint} \\\hline + & q_\texttt{mul\_decompose\_var} \cdot \BoolCheck{\mathbf{k}_i} = 0 \\\hline + & q_\texttt{mul\_decompose\_var} \cdot \Ternary{\mathbf{k}_i}{y_T - y_p}{y_T + y_p} = 0 \\\hline +\end{array} +$$ +where $\mathbf{k}_i = \mathbf{z}_{i} - 2\mathbf{z}_{i+1}$. + +## LSB + +### Layout + +$$ +\begin{array}{|c|c|c|c|c|c|c|c|c|c|c|} +a_0 & a_1 & a_2 & a_3 & a_4 & a_5 & a_6 & a_7 & a_8 & a_9 & q_\texttt{mul\_lsb} \\\hline +x_p & y_p & x_A & y_A & \lambda & \alpha & \beta & \gamma & \delta & z_1 & 1 \\\hline +x_T & y_T & x_r & y_r & & & & & & z_0 & 0 \\\hline +\end{array} +$$ + +### Constraints + +$$ +\begin{array}{|c|l|} +\hline +\text{Degree} & \text{Constraint} \\\hline + & q_\texttt{mul\_lsb} \cdot \BoolCheck{\mathbf{k}_0} = 0 \\\hline + & q_\texttt{mul\_lsb} \cdot \Ternary{\mathbf{k}_0}{x_p}{x_p - x_T} = 0 \\\hline + & q_\texttt{mul\_lsb} \cdot \Ternary{\mathbf{k}_0}{y_p}{y_p + y_T} = 0 \\\hline +\end{array} +$$ +where $\mathbf{k}_0 = \mathbf{z}_0 - 2\mathbf{z}_1$. + ## Overflow check $\mathbf{z}_i$ cannot overflow for any $i \geq 1$, because it is a weighted sum of bits only up to $2^{n-1} = 2^{253}$, which is smaller than $p$ (and also $q$). @@ -318,11 +381,11 @@ $$ \begin{array}{|c|l|} \hline \text{Degree} & \text{Constraint} \\\hline -2 & \text{q\_mul}^\text{overflow} \cdot \left(s - (\alpha + \mathbf{k}_{254} \cdot 2^{130})\right) = 0 \\\hline -2 & \text{q\_mul}^\text{overflow} \cdot \left(\mathbf{z}_0 - \alpha - t_q\right) = 0 \\\hline -3 & \text{q\_mul}^\text{overflow} \cdot \left(\mathbf{k}_{254} \cdot (\mathbf{z}_{130} - 2^{124})\right) = 0 \\\hline -3 & \text{q\_mul}^\text{overflow} \cdot \left(\mathbf{k}_{254} \cdot (s - \sum\limits_{i=0}^{129} 2^i \cdot \mathbf{s}_i)/2^{130}\right) = 0 \\\hline -5 & \text{q\_mul}^\text{overflow} \cdot \left((1 - \mathbf{k}_{254}) \cdot (1 - \mathbf{z}_{130} \cdot \eta) \cdot (s - \sum\limits_{i=0}^{129} 2^i \cdot \mathbf{s}_i)/2^{130}\right) = 0 \\\hline +2 & q_\texttt{mul\_overflow} \cdot \left(s - (\alpha + \mathbf{k}_{254} \cdot 2^{130})\right) = 0 \\\hline +2 & q_\texttt{mul\_overflow} \cdot \left(\mathbf{z}_0 - \alpha - t_q\right) = 0 \\\hline +3 & q_\texttt{mul\_overflow} \cdot \left(\mathbf{k}_{254} \cdot (\mathbf{z}_{130} - 2^{124})\right) = 0 \\\hline +3 & q_\texttt{mul\_overflow} \cdot \left(\mathbf{k}_{254} \cdot (s - \sum\limits_{i=0}^{129} 2^i \cdot \mathbf{s}_i)/2^{130}\right) = 0 \\\hline +5 & q_\texttt{mul\_overflow} \cdot \left((1 - \mathbf{k}_{254}) \cdot (1 - \mathbf{z}_{130} \cdot \eta) \cdot (s - \sum\limits_{i=0}^{129} 2^i \cdot \mathbf{s}_i)/2^{130}\right) = 0 \\\hline \end{array} $$ where $(s - \sum\limits_{i=0}^{129} 2^i \cdot \mathbf{s}_i)/2^{130}$ can be computed by another running sum. Note that the factor of $1/2^{130}$ has no effect on the constraint, since the RHS is zero. diff --git a/book/src/design/gadgets/ecc/witnessing-points.md b/book/src/design/gadgets/ecc/witnessing-points.md new file mode 100644 index 0000000000..1159f37d16 --- /dev/null +++ b/book/src/design/gadgets/ecc/witnessing-points.md @@ -0,0 +1,35 @@ +# Witnessing points + +We represent elliptic curve points in the circuit in their affine representation $(x, y)$. +The identity is represented as the pseudo-coordinate $(0, 0)$, which we +[assume](../ecc.md#chip-assumptions) is not a valid point on the curve. + +## Non-identity points + +To constrain a coordinate pair $(x, y)$ as representing a valid point on the curve, we +directly check the curve equation. For Pallas and Vesta, this is: + +$$y^2 = x^3 + 5$$ + +$$ +\begin{array}{|c|l|} +\hline +\text{Degree} & \text{Constraint} \\\hline +4 & q_\text{point}^\text{non-id} \cdot (y^2 - x^3 - 5) = 0 \\\hline +\end{array} +$$ + +## Points including the identity + +To allow $(x, y)$ to represent either a valid point on the curve, or the pseudo-coordinate +$(0, 0)$, we define a separate gate that enforces the curve equation check unless both $x$ +and $y$ are zero. + +$$ +\begin{array}{|c|l|} +\hline +\text{Degree} & \text{Constraint} \\\hline +5 & (q_\text{point} \cdot x) \cdot (y^2 - x^3 - 5) = 0 \\\hline +5 & (q_\text{point} \cdot y) \cdot (y^2 - x^3 - 5) = 0 \\\hline +\end{array} +$$ diff --git a/book/src/design/gadgets/sha256/table16.md b/book/src/design/gadgets/sha256/table16.md index d9b241e42c..00c1ad6692 100644 --- a/book/src/design/gadgets/sha256/table16.md +++ b/book/src/design/gadgets/sha256/table16.md @@ -488,7 +488,7 @@ Output: $\Sigma_1(E) = R^{even} = R_0^{even} + 2^{16} R_1^{even}$ ### σ_0 gate #### v1 -v1 of the $\sigma_0$ gate takes in a word that's split into $(3, 4, 11, 14)$-bit chunks (already constrained by message scheduling). We refer to these chunks respectively as $(a(3), b(4), c(11), d(14)).$ $b(4$ is further split into two 2-bit chunks $b(4)^{lo},b(4)^{hi}.$ We witness the spread versions of the small chunks. We already have $\texttt{spread}(c(11))$ and $\texttt{spread}(d(14))$ from the message scheduling. +v1 of the $\sigma_0$ gate takes in a word that's split into $(3, 4, 11, 14)$-bit chunks (already constrained by message scheduling). We refer to these chunks respectively as $(a(3), b(4), c(11), d(14)).$ $b(4)$ is further split into two 2-bit chunks $b(4)^{lo},b(4)^{hi}.$ We witness the spread versions of the small chunks. We already have $\texttt{spread}(c(11))$ and $\texttt{spread}(d(14))$ from the message scheduling. $(X ⋙ 7) \oplus (X ⋙ 18) \oplus (X ≫ 3)$ is equivalent to $(X ⋙ 7) \oplus (X ⋘ 14) \oplus (X ≫ 3)$. diff --git a/book/src/design/gadgets/sinsemilla.md b/book/src/design/gadgets/sinsemilla.md index 4a1b466ba4..b5fee9fc46 100644 --- a/book/src/design/gadgets/sinsemilla.md +++ b/book/src/design/gadgets/sinsemilla.md @@ -46,7 +46,44 @@ Note that unlike a simple Pedersen commitment, this commitment scheme ($\textsf{ ## Efficient implementation The aim of the design is to optimize the number of bits that can be processed for each step of the algorithm (which requires a doubling and addition in $\mathbb{G}$) for a given table size. Using a single table of size $2^k$ group elements, we can process $k$ bits at a time. -## Constraint program +### Incomplete addition + +In each step of Sinsemilla we want to compute $A_{i+1} := (A_i \;⸭\; P_i) \;⸭\; A_i$. Let +$R_i := A_i \;⸭\; P_i$ be the intermediate result such that $A_{i+1} := A_i \;⸭\; R_i$. +Recalling the [incomplete addition formulae](ecc/addition.md#incomplete-addition): + +$$ +\begin{aligned} +x_3 &= \left(\frac{y_1 - y_2}{x_1 - x_2}\right)^2 - x_1 - x_2 \\ +y_3 &= \frac{y_1 - y_2}{x_1 - x_2} \cdot (x_1 - x_3) - y_1 \\ +\end{aligned} +$$ + +Let $\lambda = \frac{y_1 - y_2}{x_1 - x_2}$. Substituting the coordinates for each of the +incomplete additions in turn, and rearranging, we get + +$$ +\begin{aligned} +\lambda_{1,i} &= \frac{y_{A,i} - y_{P,i}}{x_{A,i} - x_{P,i}} \\ +&\implies y_{A,i} - y_{P,i} = \lambda_{1,i} \cdot (x_{A,i} - x_{P,i}) \\ +&\implies y_{P,i} = y_{A,i} - \lambda_{1,i} \cdot (x_{A,i} - x_{P,i}) \\ +x_{R,i} &= \lambda_{1,i}^2 - x_{A,i} - x_{P,i} \\ +y_{R,i} &= \lambda_{1,i} \cdot (x_{A,i} - x_{R,i}) - y_{A,i} \\ +\end{aligned} +$$ +and +$$ +\begin{aligned} +\lambda_{2,i} &= \frac{y_{A,i} - y_{R,i}}{x_{A,i} - x_{R,i}} \\ +&\implies y_{A,i} - y_{R,i} = \lambda_{2,i} \cdot (x_{A,i} - x_{R,i}) \\ +&\implies y_{A,i} - \left( \lambda_{1,i} \cdot (x_{A,i} - x_{R,i}) - y_{A,i} \right) = \lambda_{2,i} \cdot (x_{A,i} - x_{R,i}) \\ +&\implies 2 \cdot y_{A,i} = (\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - x_{R,i}) \\ +x_{A,i+1} &= \lambda_{2,i}^2 - x_{A,i} - x_{R,i} \\ +y_{A,i+1} &= \lambda_{2,i} \cdot (x_{A,i} - x_{A,i+1}) - y_{A,i}. \\ +\end{aligned} +$$ + +### Constraint program Let $\mathcal{P} = \left\{(j,\, x_{P[j]},\, y_{P[j]}) \text{ for } j \in \{0..2^k - 1\}\right\}$. Input: $m_{1..=n}$. (The message words are 1-indexed here, as in the [protocol spec](https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash), but we start the loop from $i = 0$ so that $(x_{A,i}, y_{A,i})$ corresponds to $\mathsf{Acc}_i$ in the protocol spec.) @@ -70,15 +107,77 @@ We have an $n$-bit message $m = m_1 + 2^k m_2 + ... + 2^{k\cdot (n-1)} m_n$. (No Initialise the running sum $z_0 = \alpha$ and define $z_{i + 1} := \frac{z_{i} - m_{i+1}}{2^K}$. We will end up with $z_n = 0.$ -Rearranging gives us an expression for each word of the original message $m_{i+1} = z_{i} - 2^k \cdot z_{i + 1}$, which we can look up in the table. +Rearranging gives us an expression for each word of the original message +$m_{i+1} = z_{i} - 2^k \cdot z_{i + 1}$, which we can look up in the table. We position +$z_{i}$ and $z_{i + 1}$ in adjacent rows of the same column, so we can sequentially apply +the constraint across the entire message. In other words, $z_{n-i} = \sum\limits_{h=0}^{i-1} 2^{kh} \cdot m_{h+1}$. > For a little-endian decomposition as used here, the running sum is initialized to the scalar and ends at 0. For a big-endian decomposition as used in [variable-base scalar multiplication](https://hackmd.io/o9EzZBwxSWSi08kQ_fMIOw), the running sum would start at 0 and end with recovering the original scalar. -> -> The running sum only applies to message words within a single field element, i.e. if $n \geq \mathtt{PrimeField::NUM\_BITS}$ then we will have several disjoint running sums. A longer message can be constructed by splitting the message words across several field elements, and then running several instances of the constraints below. An additional $q_{S2}$ selector is set to $0$ for the last step of each element, except for the last element where it is set to $2$. -> -> In order to support chaining multiple field elements without a gap, we will use a slightly more complicated expression for $m_{i+1}$ that effectively forces $\mathbf{z}_n$ to zero for the last step of each element, as indicated by $q_{S2}$. This allows the cell that would have been $\mathbf{z}_n$ to be used to reinitialize the running sum for the next element. + +### Efficient packing + +The running sum only applies to message words within a single field element. That means if +$n \geq \mathtt{PrimeField::NUM\_BITS}$ then we will need several disjoint running sums. A +longer message can be constructed by splitting the message words across several field +elements, and then running several instances of the constraints below. + +The expression for $m_{i+1}$ above requires $n + 1$ rows in the $z_{i}$ column, leaving a +one-row gap in adjacent columns and making $\mathsf{Acc}_i$ tricker to accumulate. In +order to support chaining multiple field elements without a gap, we use a slightly more +complicated expression for $m_{i+1}$ that includes a selector: + +$$m_{i+1} = z_{i} - 2^k \cdot q_{run,i} \cdot z_{i+1}$$ + +This effectively forces $\mathbf{z}_n$ to zero for the last step of each element, which +allows the cell that would have been $\mathbf{z}_n$ to be used to reinitialize the running +sum for the next element. + +With this sorted out, the incomplete addition accumulator can eliminate $y_{A,i}$ almost +entirely, by substituting for $x$ and $\lambda$ values in the current and next rows. The +two exceptions are at the start of Sinsemilla (where we need to constrain the accumulator +to have initial value $Q$), and the end (where we need to witness $y_{A,n}$ for use +outside of Sinsemilla). + +### Selectors + +We need a total of four logical selectors to: + +- Control the Sinsemilla gate and lookup. +- Distinguish between the last message word in a running sum and its earlier words. +- Mark the start of Sinsemilla. +- Mark the end of Sinsemilla. + +We use regular selector columns for the Sinsemilla gate selector $q_{S1}$ and Sinsemilla +start selector $q_{S4}.$ The other two selectors are synthesized from a single fixed +column $q_{S2}$ as follows: + +$$ +\begin{aligned} +q_{S3} &= q_{S2} \cdot (q_{S2} - 1) \\ +q_{run} &= q_{S2} - q_{S3} \\ +\end{aligned} +$$ + +$$ +\begin{array}{|c|c|c|} +\hline +q_{S2} & q_{S3} & q_{run} \\\hline + 0 & 0 & 0 \\\hline + 1 & 0 & 1 \\\hline + 2 & 2 & 0 \\\hline +\end{array} +$$ + +We set $q_{S2}$ to $1$ on most Sinsemilla rows, and $0$ for the last step of each element, +except for the last element where it is set to $2$. We can then use $q_{S3}$ to toggle +between constraining the substituted $y_{A,i+1}$ on adjacent rows, and the witnessed +$y_{A,n}$ at the end of Sinsemilla: + +$$ +\lambda_{2,i} \cdot (x_{A,i} - x_{A,i+1}) = y_{A,i} + \frac{2 - q_{S3}}{2} \cdot y_{A,i+1} + \frac{q_{S3}}{2} \cdot y_{A,n} +$$ ### Generator lookup table The Sinsemilla circuit makes use of $2^{10}$ pre-computed random generators. These are loaded into a lookup table: @@ -95,26 +194,25 @@ $$ $$ ### Layout -Note: $q_{S3}$ is synthesized from $q_{S1}$ and $q_{S2}$; it is shown here only for clarity. $$ -\begin{array}{|c|c|c|c|c|c|c|c|c|c|c|} +\begin{array}{|c|c|c|c|c|c|c|c|c|c|} \hline -\text{Step} & x_A & x_P & bits & \lambda_1 & \lambda_2 & q_{S1} & q_{S2} & q_{S3} & q_{S4} & \textsf{fixed\_y\_Q}\\\hline - 0 & x_Q & x_{P[m_1]} & z_0 & \lambda_{1,0} & \lambda_{2,0} & 1 & 1 & 0 & 1 & y_Q \\\hline - 1 & x_{A,1} & x_{P[m_2]} & z_1 & \lambda_{1,1} & \lambda_{2,1} & 1 & 1 & 0 & 0 & 0 \\\hline - 2 & x_{A,2} & x_{P[m_3]} & z_2 & \lambda_{1,2} & \lambda_{2,2} & 1 & 1 & 0 & 0 & 0 \\\hline - \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & 1 & 1 & 0 & 0 & 0 \\\hline - n-1 & x_{A,n-1} & x_{P[m_n]} & z_{n-1} & \lambda_{1,n-1} & \lambda_{2,n-1} & 1 & 0 & 0 & 0 & 0 \\\hline - 0' & x'_{A,0} & x_{P[m'_1]} & z'_0 & \lambda'_{1,0} & \lambda'_{2,0} & 1 & 1 & 0 & 0 & 0 \\\hline - 1' & x'_{A,1} & x_{P[m'_2]} & z'_1 & \lambda'_{1,1} & \lambda'_{2,1} & 1 & 1 & 0 & 0 & 0 \\\hline - 2' & x'_{A,2} & x_{P[m'_3]} & z'_2 & \lambda'_{1,2} & \lambda'_{2,2} & 1 & 1 & 0 & 0 & 0 \\\hline - \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & 1 & 1 & 0 & 0 & 0 \\\hline - n-1' & x'_{A,n-1} & x_{P[m'_n]} & z'_{n-1} & \lambda'_{1,n-1} & \lambda'_{2,n-1} & 1 & 2 & 2 & 0 & 0 \\\hline - n' & x'_{A,n} & & & y_{A,n} & & 0 & 0 & 0 & 0 & 0 \\\hline +\text{Step} & x_A & x_P & bits & \lambda_1 & \lambda_2 & q_{S1} & q_{S2} & q_{S4} & \textsf{fixed\_y\_Q}\\\hline + 0 & x_Q & x_{P[m_1]} & z_0 & \lambda_{1,0} & \lambda_{2,0} & 1 & 1 & 1 & y_Q \\\hline + 1 & x_{A,1} & x_{P[m_2]} & z_1 & \lambda_{1,1} & \lambda_{2,1} & 1 & 1 & 0 & 0 \\\hline + 2 & x_{A,2} & x_{P[m_3]} & z_2 & \lambda_{1,2} & \lambda_{2,2} & 1 & 1 & 0 & 0 \\\hline + \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & 1 & 1 & 0 & 0 \\\hline + n-1 & x_{A,n-1} & x_{P[m_n]} & z_{n-1} & \lambda_{1,n-1} & \lambda_{2,n-1} & 1 & 0 & 0 & 0 \\\hline + 0' & x'_{A,0} & x_{P[m'_1]} & z'_0 & \lambda'_{1,0} & \lambda'_{2,0} & 1 & 1 & 0 & 0 \\\hline + 1' & x'_{A,1} & x_{P[m'_2]} & z'_1 & \lambda'_{1,1} & \lambda'_{2,1} & 1 & 1 & 0 & 0 \\\hline + 2' & x'_{A,2} & x_{P[m'_3]} & z'_2 & \lambda'_{1,2} & \lambda'_{2,2} & 1 & 1 & 0 & 0 \\\hline + \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & 1 & 1 & 0 & 0 \\\hline + n-1' & x'_{A,n-1} & x_{P[m'_n]} & z'_{n-1} & \lambda'_{1,n-1} & \lambda'_{2,n-1} & 1 & 2 & 0 & 0 \\\hline + n' & x'_{A,n} & & & y_{A,n} & & 0 & 0 & 0 & 0 \\\hline \end{array} $$ -$x_Q$, $z_0$, $z'_0$, etc. would be copied in using equality constraints. +$x_Q$, $z_0$, $z'_0$, etc. are copied in using equality constraints. ### Optimized Sinsemilla gate $$ @@ -122,12 +220,14 @@ $$ \text{For } i \in [0, n), \text{ let} &x_{R,i} &=& \lambda_{1,i}^2 - x_{A,i} - x_{P,i} \\ &Y_{A,i} &=& (\lambda_{1,i} + \lambda_{2,i}) \cdot (x_{A,i} - x_{R,i}) \\ &y_{P,i} &=& Y_{A,i}/2 - \lambda_{1,i} \cdot (x_{A,i} - x_{P,i}) \\ - &m_{i+1} &=& z_{i} - 2^k \cdot (q_{S2,i} - q_{S3,i}) \cdot z_{i+1} \\ + &m_{i+1} &=& z_{i} - q_{run,i} \cdot z_{i+1} \cdot 2^k \\ + &q_{run} &=& q_{S2} - q_{S3} \\ &q_{S3} &=& q_{S2} \cdot (q_{S2} - 1) \end{array} $$ -The Halo 2 circuit API can automatically substitute $y_{P,i}$, $x_{R,i}$, $y_{A,i}$, and $y_{A,i+1}$, so we don't need to do that manually. +The Halo 2 circuit API can automatically substitute $y_{P,i}$, $x_{R,i}$, $Y_{A,i}$, and +$Y_{A,i+1}$, so we don't need to do that manually. - $x_{A,0} = x_Q$ - $2 \cdot y_Q = Y_{A,0}$ diff --git a/book/src/design/gadgets/sinsemilla/merkle-crh.md b/book/src/design/gadgets/sinsemilla/merkle-crh.md index fc8e6a10f0..774c7eea4d 100644 --- a/book/src/design/gadgets/sinsemilla/merkle-crh.md +++ b/book/src/design/gadgets/sinsemilla/merkle-crh.md @@ -10,27 +10,58 @@ where: - ${\textsf{left}\star} = \textsf{I2LEBSP}_{\ell_{\textsf{Merkle}}^{\textsf{Orchard}}}(\textsf{left})$, - ${\textsf{right}\star} = \textsf{I2LEBSP}_{\ell_{\textsf{Merkle}}^{\textsf{Orchard}}}(\textsf{right})$, -with $\ell_{\textsf{Merkle}}^{\textsf{Orchard}} = 255.$ $\textsf{left}$ and $\textsf{right}$ are allowed to be non-canonical $255$-bit encodings. +with $\ell_{\textsf{Merkle}}^{\textsf{Orchard}} = 255.$ $\textsf{left}\star$ and +$\textsf{right}\star$ are allowed to be non-canonical $255$-bit encodings of +$\textsf{left}$ and $\textsf{right}$. -We break these inputs into the following `MessagePiece`s: +Sinsemilla operates on multiples of 10 bits, so we start by decomposing the message into +chunks: $$ \begin{aligned} -a \text{ (250 bits)} &= a_0 \,||\, a_1 \\ - &= {l\star} \,||\, (\text{bits } 0..=239 \text{ of } \textsf{ left }) \\ -b \text{ (20 bits)} &= b_0 \,||\, b_1 \,||\, b_2 \\ - &= (\text{bits } 240..=249 \text{ of } \textsf{left}) \,||\, (\text{bits } 250..=254 \text{ of } \textsf{left}) \,||\, (\text{bits } 0..=4 \text{ of } \textsf{right}) \\ -c \text{ (250 bits)} &= \text{bits } 5..=254 \text{ of } \textsf{right} +l\star &= a_0 \\ +\textsf{left}\star &= a_1 \bconcat b_0 \bconcat b_1 \\ + &= (\text{bits } 0..=239 \text{ of } \textsf{ left }) \bconcat + (\text{bits } 240..=249 \text{ of } \textsf{left}) \bconcat + (\text{bits } 250..=254 \text{ of } \textsf{left}) \\ +\textsf{right}\star &= b_2 \bconcat c \\ + &= (\text{bits } 0..=4 \text{ of } \textsf{right}) \bconcat + (\text{bits } 5..=254 \text{ of } \textsf{right}) \end{aligned} $$ -$a,b,c$ are constrained by the $\textsf{SinsemillaHash}$ to be $250$ bits, $20$ bits, and $250$ bits respectively. +Then we recompose the chunks into `MessagePiece`s: -In a custom gate, we check this message decomposition by enforcing the following constraints: +$$ +\begin{array}{|c|l|} +\hline +\text{Length (bits)} & \text{Piece} \\\hline +250 & a = a_0 \bconcat a_1 \\ +20 & b = b_0 \bconcat b_1 \bconcat b_2 \\ +250 & c \\\hline +\end{array} +$$ + +Each message piece is constrained by $\SinsemillaHash$ to its stated length. Additionally, +$\textsf{left}$ and $\textsf{right}$ are witnessed as field elements, so we know that they +are canonical. However, we need additional constraints to enforce that the chunks are the +correct bit lengths (or else they could overlap in the decompositions and allow the prover +to witness an arbitrary $\SinsemillaHash$ message). + +Some of these constraints can be implemented with reusable circuit gadgets. We define a +custom gate controlled by the selector $q_\mathsf{decompose}$ to hold the remaining +constraints. + +## Bit length constraints + +Chunk $c$ is directly constrained by Sinsemilla. We constrain the remaining chunks with +the following constraints: -1. $a_0 = l$ -
-$z_{1,a}$, the index-1 running sum output of $\textsf{SinsemillaHash}(a)$, is copied into the gate. $z_{1,a}$ has been constrained by the $\textsf{SinsemillaHash}$ to be $240$ bits. We recover the subpieces $a_0, a_1$ using $a, z_{1,a}$: +### $a_0, a_1$ + +$z_{1,a}$, the index-1 running sum output of $\textsf{SinsemillaHash}(a)$, is copied into +the gate. $z_{1,a}$ has been constrained by $\textsf{SinsemillaHash}$ to be $240$ bits, +and is precisely $a_1$. We recover chunk $a_0$ using $a, z_{1,a}:$ $$ \begin{aligned} z_{1,a} &= \frac{a - a_0}{2^{10}}\\ @@ -38,12 +69,13 @@ z_{1,a} &= \frac{a - a_0}{2^{10}}\\ \implies a_0 &= a - z_{1,a} \cdot 2^{10}. \end{aligned} $$ -$l + 1$ is loaded into a fixed column at each layer of the hash. It is used both as a gate selector, and to fix the value of $l$. We check that $$a_0 = (l + 1) - 1.$$ -> Note: The reason for using $l + 1$ instead of $l$ is that $l = 0$ when $\textsf{layer} = 31$ (hashing two leaves). We cannot have a zero-valued selector, since a constraint gated by a zero-valued selector is never checked. -2. $b_1 + 2^5 \cdot b_2 = z_{1,b}$ -
-$z_{1,b}$, the index-1 running sum output of $\textsf{SinsemillaHash}(b)$, is copied into the gate. $z_{1,b}$ has been constrained by the $\textsf{SinsemillaHash}$ to be $10$ bits. We witness the subpieces $b_1, b_2$ outside this gate, and constrain them each to be $5$ bits. Inside the gate, we check that $$b_1 + 2^5 \cdot b_2 = z_{1,b}.$$ +### $b_0, b_1, b_2$ + +$z_{1,b}$, the index-1 running sum output of $\textsf{SinsemillaHash}(b)$, is copied into +the gate. $z_{1,b}$ has been constrained by $\textsf{SinsemillaHash}$ to be $10$ bits. We +witness the subpieces $b_1, b_2$ outside this gate, and constrain them each to be $5$ +bits. Inside the gate, we check that $$b_1 + 2^5 \cdot b_2 = z_{1,b}.$$ We also recover the subpiece $b_0$ using $(b, z_{1,b})$: $$ \begin{aligned} @@ -52,18 +84,61 @@ z_{1,b} &= \frac{b - b_{0..=10}}{2^{10}}\\ \end{aligned} $$ +### Constraints + +$$ +\begin{array}{|c|l|} +\hline +\text{Degree} & \text{Constraint} \\\hline + & \ShortLookupRangeCheck{b_1, 5} \\\hline + & \ShortLookupRangeCheck{b_2, 5} \\\hline +2 & q_\mathsf{decompose} \cdot (z_{1,b} - (b_1 + b_2 \cdot 2^5)) = 0 \\\hline +\end{array} +$$ + +where $\ShortLookupRangeCheck{}$ is a +[short lookup range check](../decomposition.md#short-range-check). + +## Decomposition constraints + We have now derived or witnessed every subpiece, and range-constrained every subpiece: - $a_0$ ($10$ bits), derived as $a_0 = a - 2^{10} \cdot z_{1,a}$; - $a_1$ ($240$ bits), equal to $z_{1,a}$; - $b_0$ ($10$ bits), derived as $b_0 = b - 2^{10} \cdot z_{1,b}$; - $b_1$ ($5$ bits) is witnessed and constrained outside the gate; - $b_2$ ($5$ bits) is witnessed and constrained outside the gate; -- $b_1 + 2^5 \cdot b_2$ is constrained to equal $z_{1, b}$, -and we use them to reconstruct the original field element inputs: +- $c$ ($250$ bits) is witnessed and constrained outside the gate. +- $b_1 + 2^5 \cdot b_2$ is constrained to equal $z_{1, b}$. + +We can now use them to reconstruct the original field element inputs: -3. $\mathsf{left} = a_1 + 2^{240} \cdot b_0 + 2^{254} \cdot b_1$ +$$ +\begin{align} +l &= a_0 \\ +\mathsf{left} &= a_1 + 2^{240} \cdot b_0 + 2^{250} \cdot b_1 \\ +\mathsf{right} &= b_2 + 2^5 \cdot c +\end{align} +$$ -4. $\mathsf{right} = b_2 + 2^5 \cdot c$ +$$ +\begin{array}{|c|l|} +\hline +\text{Degree} & \text{Constraint} \\\hline +2 & q_\mathsf{decompose} \cdot (a_0 - l) = 0 \\\hline +2 & q_\mathsf{decompose} \cdot (a_1 + (b_0 + b_1 \cdot 2^{10}) \cdot 2^{240} - \mathsf{left}) = 0 \\\hline +2 & q_\mathsf{decompose} \cdot (b_2 + c \cdot 2^5 - \mathsf{right}) = 0 \\\hline +\end{array} +$$ + +## Region layout + +$$ +\begin{array}{|c|c|c|c|c|c} + & & & & & q_\mathsf{decompose} \\\hline + a & b & c & \mathsf{left} & \mathsf{right} & 1 \\\hline +z_{1,a} &z_{1,b} & b_1 & b_2 & l & 0 \\\hline +\end{array} +$$ ## Circuit components The Orchard circuit spans $10$ advice columns while the $\textsf{Sinsemilla}$ chip only uses $5$ advice columns. We distribute the path hashing evenly across two $\textsf{Sinsemilla}$ chips to make better use of the available circuit area. Since the output from the previous layer hash is copied into the next layer hash, we maintain continuity even when moving from one chip to the other. diff --git a/book/src/design/implementation/proofs.md b/book/src/design/implementation/proofs.md index d06a3adf9a..4b1339fb73 100644 --- a/book/src/design/implementation/proofs.md +++ b/book/src/design/implementation/proofs.md @@ -36,8 +36,8 @@ the cost of deserialization, which isn't negligible due to point compression. A Halo 2 proof, constructed over a curve $E(\mathbb{F}_p)$, is encoded as a stream of: -- Points $P \in E(\mathbb{F}_p)$) (for commitments to polynomials), and -- Scalars $s \in \mathbb{F}_q$) (for evaluations of polynomials, and blinding values). +- Points $P \in E(\mathbb{F}_p)$ (for commitments to polynomials), and +- Scalars $s \in \mathbb{F}_q$ (for evaluations of polynomials, and blinding values). For the Pallas and Vesta curves, both points and scalars have 32-byte encodings, meaning that proofs are always a multiple of 32 bytes. diff --git a/book/src/design/implementation/selector-combining.md b/book/src/design/implementation/selector-combining.md new file mode 100644 index 0000000000..a6e98a9529 --- /dev/null +++ b/book/src/design/implementation/selector-combining.md @@ -0,0 +1,116 @@ +# Selector combining + +Heavy use of custom gates can lead to a circuit defining many binary selectors, which +would increase proof size and verification time. + +This section describes an optimization, applied automatically by halo2, that combines +binary selector columns into fewer fixed columns. + +The basic idea is that if we have $\ell$ binary selectors labelled $1, \ldots, \ell$ that +are enabled on disjoint sets of rows, then under some additional conditions we can combine +them into a single fixed column, say $q$, such that: +$$ +q = \begin{cases} + k, &\text{if the selector labelled } k \text{ is } 1 \\ + 0, &\text{if all these selectors are } 0. +\end{cases} +$$ + +However, the devil is in the detail. + +The halo2 API allows defining some selectors to be "simple selectors", subject to the +following condition: + +> Every polynomial constraint involving a simple selector $s$ must be of the form +> $s \cdot t = 0,$ where $t$ is a polynomial involving *no* simple selectors. + +Suppose that $s$ has label $k$ in some set of $\ell$ simple selectors that are combined +into $q$ as above. Then this condition ensures that replacing $s$ by +$q \cdot \prod_{1 \leq h \leq \ell,\,h \neq k}\; (h - q)$ will not change the meaning of +any constraints. + +> It would be possible to relax this condition by ensuring that every use of a binary +> selector is substituted by a precise interpolation of its value from the corresponding +> combined selector. However, +> +> * the restriction simplifies the implementation, developer tooling, and human +> understanding and debugging of the resulting constraint system; +> * the scope to apply the optimization is not impeded very much by this restriction for +> typical circuits. + +Note that replacing $s$ by $q \cdot \prod_{1 \leq h \leq \ell,\,h \neq k}\; (h - q)$ will +increase the degree of constraints selected by $s$ by $\ell-1$, and so we must choose the +selectors that are combined in such a way that the maximum degree bound is not exceeded. + +## Identifying selectors that can be combined + +We need a partition of the overall set of selectors $s_0, \ldots, s_{m-1}$ into subsets +(called "combinations"), such that no two selectors in a combination are enabled on the +same row. + +Labels must be unique within a combination, but they are not unique across combinations. +Do not confuse a selector's index with its label. + +Suppose that we are given $\mathsf{max\_degree}$, the degree bound of the circuit. + +We use the following algorithm: + +1. Leave nonsimple selectors unoptimized, i.e. map each of them to a separate fixed + column. +2. Check (or ensure by construction) that all polynomial constraints involving each simple + selector $s_i$ are of the form $s_i \cdot t_{i,j} = 0$ where $t_{i,j}$ do not involve + any simple selectors. For each $i$, record the maximum degree of any $t_{i,j}$ as + $d^\mathsf{max}_i$. +3. Compute a binary "exclusion matrix" $X$ such that $X_{j,i}$ is $1$ whenever $i \neq j$ + and $s_i$ and $s_j$ are enabled on the same row; and $0$ otherwise. + > Since $X$ is symmetric and is zero on the diagonal, we can represent it by either its + > upper or lower triangular entries. The rest of the algorithm is guaranteed only to + > access only the entries $X_{j,i}$ where $j > i$. +4. Initialize a boolean array $\mathsf{added}_{0..{k-1}}$ to all $\mathsf{false}$. + > $\mathsf{added}_i$ will record whether $s_i$ has been included in any combination. +6. Iterate over the $s_i$ that have not yet been added to any combination: + * a. Add $s_i$ to a fresh combination $c$, and set $\mathsf{added}_i = \mathsf{true}$. + * b. Let mut $d := d^\mathsf{max}_i - 1$. + > $d$ is used to keep track of the largest degree, *excluding* the selector + > expression, of any gate involved in the combination $c$ so far. + * c. Iterate over all the selectors $s_j$ for $j > i$ that can potentially join $c$, + i.e. for which $\mathsf{added}_j$ is false: + * i. (Optimization) If $d + \mathsf{len}(c) = \mathsf{max\_degree}$, break to the + outer loop, since no more selectors can be added to $c$. + * ii. Let $d^\mathsf{new} = \mathsf{max}(d, d^\mathsf{max}_j-1)$. + * iii. If $X_{j,i'}$ is $\mathsf{true}$ for any $i'$ in $c$, or if + $d^\mathsf{new} + (\mathsf{len}(c) + 1) > \mathsf{max\_degree}$, break to the outer + loop. + > $d^\mathsf{new} + (\mathsf{len}(c) + 1)$ is the maximum degree, *including* the + > selector expression, of any constraint that would result from adding $s_j$ to the + > combination $c$. + * iv. Set $d := d^\mathsf{new}$. + * v. Add $s_j$ to $c$ and set $\mathsf{added}_j := \mathsf{true}$. + * d. Allocate a fixed column $q_c$, initialized to all-zeroes. + * e. For each selector $s' \in c$: + * i. Label $s'$ with a distinct index $k$ where $1 \leq k \leq \mathsf{len}(c)$. + * ii. Record that $s'$ should be substituted with + $q_c \cdot \prod_{1 \leq h \leq \mathsf{len}(c),\,h \neq k} (h-q_c)$ in all gate + constraints. + * iii. For each row $r$ such that $s'$ is enabled at $r$, assign the value $k$ to + $q_c$ at row $r$. + +The above algorithm is implemented in +[halo2_proofs/src/plonk/circuit/compress_selectors.rs](https://github.com/zcash/halo2/blob/main/halo2_proofs/src/plonk/circuit/compress_selectors.rs). +This is used by the `compress_selectors` function of +[halo2_proofs/src/plonk/circuit.rs](https://github.com/zcash/halo2/blob/main/halo2_proofs/src/plonk/circuit.rs) +which does the actual substitutions. + +## Writing circuits to take best advantage of selector combining + +For this optimization it is beneficial for a circuit to use simple selectors as far as +possible, rather than fixed columns. It is usually not beneficial to do manual combining +of selectors, because the resulting fixed columns cannot take part in the automatic +combining. That means that to get comparable results you would need to do a global +optimization manually, which would interfere with writing composable gadgets. + +Whether two selectors are enabled on the same row (and so are inhibited from being +combined) depends on how regions are laid out by the floor planner. The currently +implemented floor planners do not attempt to take this into account. We suggest not +worrying about it too much — the gains that can be obtained by cajoling a floor planner to +shuffle around regions in order to improve combining are likely to be relatively small. diff --git a/book/src/design/protocol.md b/book/src/design/protocol.md index 15e3a07f78..cd15d68bf4 100644 --- a/book/src/design/protocol.md +++ b/book/src/design/protocol.md @@ -83,7 +83,7 @@ notions. correct, does the prover actually possess ("know") a valid witness? We refer to the probability that a cheating prover falsely convinces the verifier of this knowledge as the _knowledge error_. -* **Zero knowledge:** Does the prover learn anything besides that which can be +* **Zero knowledge:** Does the verifier learn anything besides that which can be inferred from the correctness of the statement and the prover's knowledge of a valid witness? @@ -123,7 +123,7 @@ easily "rewind" the verifier by forking the transcript and sending new messages to the verifier. Studying the concrete security of our construction _after_ applying this transformation is important. Fortunately, we are able to follow a framework of analysis by Ghoshal and Tessaro -([[GT20]]((https://eprint.iacr.org/2020/1351))) that has been applied to +([[GT20](https://eprint.iacr.org/2020/1351)]) that has been applied to constructions similar to ours. We will study our protocol through the notion of _state-restoration soundness_. @@ -164,7 +164,7 @@ $$ \end{array} $$ -As shown in [[GT20]]((https://eprint.iacr.org/2020/1351)) (Theorem 1) state +As shown in [[GT20](https://eprint.iacr.org/2020/1351)] (Theorem 1) state restoration soundness is tightly related to soundness after applying the Fiat-Shamir transformation. @@ -186,12 +186,12 @@ algebraic group model. > _algebraic_ if whenever it outputs a group element $X$ it also outputs a > _representation_ $\mathbf{x} \in \field^n$ such that $\langle \mathbf{x}, \mathbf{G} \rangle = X$ where $\mathbf{G} \in \group^n$ is the vector of group > elements that $\alg{\prover}$ has seen so far. -> Notationally, we write $\left[X\right]$ to describe a group element $X$ enhanced -> with this representation. We also write $[X]^{\mathbf{G}}_i$ to identify the +> Notationally, we write $\rep{X}$ to describe a group element $X$ enhanced +> with this representation. We also write $\repv{X}{G}{i}$ to identify the > component of the representation of $X$ that corresponds with $\mathbf{G}_i$. In > other words, $$ -X = \sum\limits_{i=0}^{n - 1} \left[ [X]^{\mathbf{G}}_i \right] \mathbf{G}_i +X = \sum\limits_{i=0}^{n - 1} \left[ \repv{X}{G}{i} \right] \mathbf{G}_i $$ The algebraic group model allows us to perform so-called "online" extraction for @@ -294,7 +294,7 @@ the verifier algorithm sends to the prover. Let $\omega \in \field$ be a $n = 2^k$ primitive root of unity forming the domain $D = (\omega^0, \omega^1, ..., \omega^{n - 1})$ with $t(X) = X^n - 1$ the -vanishing polynomial over this domain. Let $k, n_g, n_a$ be positive integers. +vanishing polynomial over this domain. Let $n_g, n_a, n_e$ be positive integers with $n_a, n_e \lt n$ and $n_g \geq 4$. We present an interactive argument $\halo = (\setup, \prover, \verifier)$ for the relation $$ @@ -303,19 +303,19 @@ $$ &\left( \begin{array}{ll} \left( -g(X, C_0, C_1, ..., C_{n_a - 1}) +g(X, C_0, ..., C_{n_a - 1}, a_0(X), ..., a_{n_a - 1}\left(X, C_0, ..., C_{n_a - 1}, a_0(X), ..., a_{n_a - 2}(X) \right)) \right); \\ \left( -a_0(X), a_1(X, C_0), ..., a_{n_a - 1}(X, C_0, C_1, ..., C_{n_a - 1}) +a_0(X), a_1(X, C_0, a_0(X)), ..., a_{n_a - 1}\left(X, C_0, ..., C_{n_a - 1}, a_0(X), ..., a_{n_a - 2}(X) \right) \right) \end{array} \right) : \\ \\ -& g(\omega^i, C_0, C_1, ..., C_{n_a - 1}) = 0 \, \, \, \, \forall i \in [0, 2^k) +& g(\omega^i, \cdots) = 0 \, \, \, \, \forall i \in [0, 2^k) \end{array} \right\} $$ -where $a_0, a_1, ..., a_{n_a - 1}$ are (multivariate) polynomials with degree $n - 1$ in $X$ and $g$ has degree $n_g(n - 1)$ in $X$. +where $a_0, a_1, ..., a_{n_a - 1}$ are (multivariate) polynomials with degree $n - 1$ in $X$ and $g$ has degree $n_g(n - 1)$ at most in any indeterminates $X, C_0, C_1, ...$. $\setup(\sec)$ returns $\pp = (\group, \field, \mathbf{G} \in \group^n, U, W \in \group)$. @@ -324,31 +324,31 @@ For all $i \in [0, n_a)$: * Let $\mathbf{q}$ be a list of distinct sets of integers containing $\mathbf{p_i}$ and the set $\mathbf{q_0} = \{0\}$. * Let $\sigma(i) = \mathbf{q}_j$ when $\mathbf{q}_j = \mathbf{p_i}$. -Let $n_q$ denote the size of $\mathbf{q}$, and let $n_e$ denote the size of every $\mathbf{p_i}$ without loss of generality. +Let $n_q \leq n_a$ denote the size of $\mathbf{q}$, and let $n_e$ denote the size of every $\mathbf{p_i}$ without loss of generality. -In the following protocol, we take it for granted that each polynomial $a_i(X, \cdots)$ is defined such that $n_e + 1$ blinding factors are freshly sampled by the prover and are each present as an evaluation of $a_i(X, \cdots)$ over the domain $D$. +In the following protocol, we take it for granted that each polynomial $a_i(X, \cdots)$ is defined such that $n_e + 1$ blinding factors are freshly sampled by the prover and are each present as an evaluation of $a_i(X, \cdots)$ over the domain $D$. In all of the following, the verifier's challenges cannot be zero or an element in $D$, and some additional limitations are placed on specific challenges as well. 1. $\prover$ and $\verifier$ proceed in the following $n_a$ rounds of interaction, where in round $j$ (starting at $0$) - * $\prover$ sets $a'_j(X) = a_j(X, c_0, c_1, ..., c_{j - 1})$ - * $\prover$ sends a hiding commitment $A_j = \innerprod{\mathbf{a'}}{\mathbf{G}} + [\cdot] W$ where $\mathbf{a'}$ are the coefficients of the univariate polynomial $a'_j(X)$ and $\cdot$ is some random, independently sampled blinding factor elided for exposition. + * $\prover$ sets $a'_j(X) = a_j(X, c_0, c_1, ..., c_{j - 1}, a_0(X, \cdots), ..., a_{j - 1}(X, \cdots, c_{j - 1}))$ + * $\prover$ sends a hiding commitment $A_j = \innerprod{\mathbf{a'}}{\mathbf{G}} + [\cdot] W$ where $\mathbf{a'}$ are the coefficients of the univariate polynomial $a'_j(X)$ and $\cdot$ is some random, independently sampled blinding factor elided for exposition. (This elision notation is used throughout this protocol description to simplify exposition.) * $\verifier$ responds with a challenge $c_j$. -2. $\prover$ and $\verifier$ set $g'(X) = g(X, c_0, c_1, ..., c_{n_a - 1})$. +2. $\prover$ sets $g'(X) = g(X, c_0, c_1, ..., c_{n_a - 1}, \cdots)$. 3. $\prover$ sends a commitment $R = \innerprod{\mathbf{r}}{\mathbf{G}} + [\cdot] W$ where $\mathbf{r} \in \field^n$ are the coefficients of a randomly sampled univariate polynomial $r(X)$ of degree $n - 1$. 4. $\prover$ computes univariate polynomial $h(X) = \frac{g'(X)}{t(X)}$ of degree $n_g(n - 1) - n$. 5. $\prover$ computes at most $n - 1$ degree polynomials $h_0(X), h_1(X), ..., h_{n_g - 2}(X)$ such that $h(X) = \sum\limits_{i=0}^{n_g - 2} X^{ni} h_i(X)$. 6. $\prover$ sends commitments $H_i = \innerprod{\mathbf{h_i}}{\mathbf{G}} + [\cdot] W$ for all $i$ where $\mathbf{h_i}$ denotes the vector of coefficients for $h_i(X)$. 7. $\verifier$ responds with challenge $x$ and computes $H' = \sum\limits_{i=0}^{n_g - 2} [x^{ni}] H_i$. 8. $\prover$ sets $h'(X) = \sum\limits_{i=0}^{n_g - 2} x^{ni} h_i(X)$. -9. $\prover$ sends $r = r(x)$ and for all $i \in [0, n_a)$ sends $\mathbf{a_i}$ such that $(\mathbf{a_i})_j = a'_i(\omega^{(\mathbf{p_i})_j} x)$ for all $j \in [0, n_e]$. -10. For all $i \in [0, n_a)$ $\prover$ and $\verifier$ set $s_i(X)$ to be the lowest degree univariate polynomial defined such that $s_i(\omega^{(\mathbf{p_i})_j} x) = (\mathbf{a_i})_j$ for all $j \in [0, n_e)$. +9. $\prover$ sends $r = r(x)$ and for all $i \in [0, n_a)$ sends $\mathbf{a_i}$ such that $(\mathbf{a_i})_j = a'_i(\omega^{(\mathbf{p_i})_j} x)$ for all $j \in [0, n_e - 1]$. +10. For all $i \in [0, n_a)$ $\prover$ and $\verifier$ set $s_i(X)$ to be the lowest degree univariate polynomial defined such that $s_i(\omega^{(\mathbf{p_i})_j} x) = (\mathbf{a_i})_j$ for all $j \in [0, n_e - 1)$. 11. $\verifier$ responds with challenges $x_1, x_2$ and initializes $Q_0, Q_1, ..., Q_{n_q - 1} = \zero$. * Starting at $i=0$ and ending at $n_a - 1$ $\verifier$ sets $Q_{\sigma(i)} := [x_1] Q_{\sigma(i)} + A_i$. - * $\verifier$ finally sets $Q_0 := [x_1^2] Q_i + [x_1] H' + R$. + * $\verifier$ finally sets $Q_0 := [x_1^2] Q_0 + [x_1] H' + R$. 12. $\prover$ initializes $q_0(X), q_1(X), ..., q_{n_q - 1}(X) = 0$. * Starting at $i=0$ and ending at $n_a - 1$ $\prover$ sets $q_{\sigma(i)} := x_1 q_{\sigma(i)} + a'(X)$. * $\prover$ finally sets $q_0(X) := x_1^2 q_0(X) + x_1 h'(X) + r(X)$. 13. $\prover$ and $\verifier$ initialize $r_0(X), r_1(X), ..., r_{n_q - 1}(X) = 0$. - * Starting at $i=0$ and ending at $n_a - 1$ $\prover$ and $\verifier$ set $r_{\sigma(i)}(X) := x_1 r_{\sigma(i)}(X) + s_i(X)$. + * Starting at $i = 0$ and ending at $n_a - 1$ $\prover$ and $\verifier$ set $r_{\sigma(i)}(X) := x_1 r_{\sigma(i)}(X) + s_i(X)$. * Finally $\prover$ and $\verifier$ set $r_0 := x_1^2 r_0 + x_1 h + r$ and where $h$ is computed by $\verifier$ as $\frac{g'(x)}{t(x)}$ using the values $r, \mathbf{a}$ provided by $\prover$. 14. $\prover$ sends $Q' = \innerprod{\mathbf{q'}}{\mathbf{G}} + [\cdot] W$ where $\mathbf{q'}$ defines the coefficients of the polynomial $$q'(X) = \sum\limits_{i=0}^{n_q - 1} @@ -369,7 +369,7 @@ $$ 15. $\verifier$ responds with challenge $x_3$. 16. $\prover$ sends $\mathbf{u} \in \field^{n_q}$ such that $\mathbf{u}_i = q_i(x_3)$ for all $i \in [0, n_q)$. 17. $\verifier$ responds with challenge $x_4$. -18. $\prover$ and $\verifier$ set $P = Q' + x_4 \sum\limits_{i=0}^{n_q - 1} [x_4^i] Q_i$ and $v = $ +18. $\verifier$ sets $P = Q' + x_4 \sum\limits_{i=0}^{n_q - 1} [x_4^i] Q_i$ and $v = $ $$ \sum\limits_{i=0}^{n_q - 1} \left( @@ -392,12 +392,437 @@ $$ 19. $\prover$ sets $p(X) = q'(X) + [x_4] \sum\limits_{i=0}^{n_q - 1} x_4^i q_i(X)$. 20. $\prover$ samples a random polynomial $s(X)$ of degree $n - 1$ with a root at $x_3$ and sends a commitment $S = \innerprod{\mathbf{s}}{\mathbf{G}} + [\cdot] W$ where $\mathbf{s}$ defines the coefficients of $s(X)$. 21. $\verifier$ responds with challenges $\xi, z$. -22. $\prover$ and $\verifier$ set $P' = P - [v] \mathbf{G}_0 + [\xi] S$. -23. $\prover$ sets $p'(X) = p(X) - v + \xi s(X)$. +22. $\verifier$ sets $P' = P - [v] \mathbf{G}_0 + [\xi] S$. +23. $\prover$ sets $p'(X) = p(X) - p(x_3) + \xi s(X)$ (where $p(x_3)$ should correspond with the verifier's computed value $v$). 24. Initialize $\mathbf{p'}$ as the coefficients of $p'(X)$ and $\mathbf{G'} = \mathbf{G}$ and $\mathbf{b} = (x_3^0, x_3^1, ..., x_3^{n - 1})$. $\prover$ and $\verifier$ will interact in the following $k$ rounds, where in the $j$th round starting in round $j=0$ and ending in round $j=k-1$: * $\prover$ sends $L_j = \innerprod{\mathbf{p'}_\hi}{\mathbf{G'}_\lo} + [z \innerprod{\mathbf{p'}_\hi}{\mathbf{b}_\lo}] U + [\cdot] W$ and $R_j = \innerprod{\mathbf{p'}_\lo}{\mathbf{G'}_\hi} + [z \innerprod{\mathbf{p'}_\lo}{\mathbf{b}_\hi}] U + [\cdot] W$. - * $\verifier$ responds with challenge $u_j$. - * $\prover$ and $\verifier$ set $\mathbf{G'} := \mathbf{G'}_\lo + u_j \mathbf{G'}_\hi$ and $\mathbf{b} = \mathbf{b}_\lo + u_j \mathbf{b}_\hi$. + * $\verifier$ responds with challenge $u_j$ chosen such that $1 + u_{k-1-j} x_3^{2^j}$ is nonzero. + * $\prover$ and $\verifier$ set $\mathbf{G'} := \mathbf{G'}_\lo + u_j \mathbf{G'}_\hi$ and $\mathbf{b} := \mathbf{b}_\lo + u_j \mathbf{b}_\hi$. * $\prover$ sets $\mathbf{p'} := \mathbf{p'}_\lo + u_j^{-1} \mathbf{p'}_\hi$. -25. $\prover$ sends $c = \mathbf{p'}_0$ and synthetic blinding factor $f$. +25. $\prover$ sends $c = \mathbf{p'}_0$ and synthetic blinding factor $f$ computed from the elided blinding factors. 26. $\verifier$ accepts only if $\sum_{j=0}^{k - 1} [u_j^{-1}] L_j + P' + \sum_{j=0}^{k - 1} [u_j] R_j = [c] \mathbf{G'}_0 + [c \mathbf{b}_0 z] U + [f] W$. + +### Zero-knowledge and Completeness + +We claim that this protocol is _perfectly complete_. This can be verified by +inspection of the protocol; given a valid witness $a_i(X, \cdots) \forall i$ the +prover succeeds in convincing the verifier with probability $1$. + +We claim that this protocol is _perfect special honest-verifier zero knowledge_. +We do this by showing that a simulator $\sim$ exists which can produce an +accepting transcript that is equally distributed with a valid prover's +interaction with a verifier with the same public coins. The simulator will act +as an honest prover would, with the following exceptions: + +1. In step $1$ of the protocol $\sim$ chooses random degree $n - 1$ polynomials (in $X$) $a_i(X, \cdots) \forall i$. +2. In step $5$ of the protocol $\sim$ chooses a random $n - 1$ degree polynomials $h_0(X), h_1(X), ..., h_{n_g - 2}(X)$. +3. In step $14$ of the protocol $\sim$ chooses a random $n - 1$ degree polynomial $q'(X)$. +4. In step $20$ of the protocol $\sim$ uses its foreknowledge of the verifier's choice of $\xi$ to produce a degree $n - 1$ polynomial $s(X)$ conditioned only such that $p(X) - v + \xi s(X)$ has a root at $x_3$. + +First, let us consider why this simulator always succeeds in producing an +_accepting_ transcript. $\sim$ lacks a valid witness and simply commits to +random polynomials whenever knowledge of a valid witness would be required by +the honest prover. The verifier places no conditions on the scalar values in the +transcript. $\sim$ must only guarantee that the check in step $26$ of the +protocol succeeds. It does so by using its knowledge of the challenge $\xi$ to +produce a polynomial which interferes with $p'(X)$ to ensure it has a root at +$x_3$. The transcript will thus always be accepting due to perfect completeness. + +In order to see why $\sim$ produces transcripts distributed identically to the +honest prover, we will look at each piece of the transcript and compare the +distributions. First, note that $\sim$ (just as the honest prover) uses a +freshly random blinding factor for every group element in the transcript, and so +we need only consider the _scalars_ in the transcript. $\sim$ acts just as the +prover does except in the mentioned cases so we will analyze each case: + +1. $\sim$ and an honest prover reveal $n_e$ openings of each polynomial $a_i(X, \cdots)$, and at most one additional opening of each $a_i(X, \cdots)$ in step $16$. However, the honest prover blinds their polynomials $a_i(X, \cdots)$ (in $X$) with $n_e + 1$ random evaluations over the domain $D$. Thus, the openings of $a_i(X, \cdots)$ at the challenge $x$ (which is prohibited from being $0$ or in the domain $D$ by the protocol) are distributed identically between $\sim$ and an honest prover. +2. Neither $\sim$ nor the honest prover reveal $h(x)$ as it is computed by the verifier. However, the honest prover may reveal $h'(x_3)$ --- which has a non-trivial relationship with $h(X)$ --- were it not for the fact that the honest prover also commits to a random degree $n - 1$ polynomial $r(X)$ in step $3$, producing a commitment $R$ and ensuring that in step $12$ when the prover sets $q_0(X) := x_1^2 q_0(X) + x_1 h'(X) + r(X)$ the distribution of $q_0(x_3)$ is uniformly random. Thus, $h'(x_3)$ is never revealed by the honest prover nor by $\sim$. +3. The expected value of $q'(x_3)$ is computed by the verifier (in step $18$) and so the simulator's actual choice of $q'(X)$ is irrelevant. +4. $p(X) - v + \xi s(X)$ is conditioned on having a root at $x_3$, but otherwise no conditions are placed on $s(X)$ and so the distribution of the degree $n - 1$ polynomial $p(X) - v + \xi s(X)$ is uniformly random whether or not $s(X)$ has a root at $x_3$. Thus, the distribution of $c$ produced in step $25$ is identical between $\sim$ and an honest prover. The synthetic blinding factor $f$ also revealed in step $25$ is a trivial function of the prover's other blinding factors and so is distributed identically between $\sim$ and an honest prover. + +Notes: + +1. In an earlier version of our protocol, the prover would open each individual commitment $H_0, H_1, ...$ at $x$ as part of the multipoint opening argument, and the verifier would confirm that a linear combination of these openings (with powers of $x^n$) agreed to the expected value of $h(x)$. This was done because it's more efficient in recursive proofs. However, it was unclear to us what the expected distribution of the openings of these commitments $H_0, H_1, ...$ was and so proving that the argument was zero-knowledge is difficult. Instead, we changed the argument so that the _verifier_ computes a linear combination of the commitments and that linear combination is opened at $x$. This avoided leaking $h_i(x)$. +2. As mentioned, in step $3$ the prover commits to a random polynomial as a way of ensuring that $h'(x_3)$ is not revealed in the multiopen argument. This is done because it's unclear what the distribution of $h'(x_3)$ would be. +3. Technically it's also possible for us to prove zero-knowledge with a simulator that uses its foreknowledge of the challenge $x$ to commit to an $h(X)$ which agrees at $x$ to the value it will be expected to. This would obviate the need for the random polynomial $s(X)$ in the protocol. This may make the analysis of zero-knowledge for the remainder of the protocol a little bit tricky though, so we didn't go this route. +4. Group element blinding factors are _technically_ not necessary after step $23$ in which the polynomial is completely randomized. However, it's simpler in practice for us to ensure that every group element in the protocol is randomly blinded to make edge cases involving the point at infinity harder. +5. It is crucial that the verifier cannot challenge the prover to open polynomials at points in $D$ as otherwise the transcript of an honest prover will be forced to contain what could be portions of the prover's witness. We therefore restrict the space of challenges to include all elements of the field except $D$ and, for simplicity, we also prohibit the challenge of $0$. + +## Witness-extended Emulation + +Let $\protocol = \protocol[\group]$ be the interactive argument described above for relation $\relation$ and some group $\group$ with scalar field $\field$. We can always construct an extractor $\extractor$ such that for any non-uniform algebraic prover $\alg{\prover}$ making at most $q$ queries to its oracle, there exists a non-uniform adversary $\dlreladv$ with the property that for any computationally unbounded distinguisher $\distinguisher$ + +$$ +\adv^\srwee_{\protocol, \relation}(\alg{\prover}, \distinguisher, \extractor, \sec) \leq q\epsilon + \adv^\dlrel_{\group,n+2}(\dlreladv, \sec) +$$ + +where $\epsilon \leq \frac{n_g \cdot (n - 1)}{|\ch|}$. + +_Proof._ We will prove this by invoking Theorem 1 of [[GT20]](https://eprint.iacr.org/2020/1351). First, we note that the challenge space for all rounds is the same, i.e. $\forall i \ \ch = \ch_i$. Theorem 1 requires us to define: + +- $\badch(\tr') \in \ch$ for all partial transcripts $\tr' = (\pp, x, [a_0], c_0, \ldots, [a_i])$ such that $|\badch(\tr')| / |\ch| \leq \epsilon$. +- an extractor function $e$ that takes as input an accepting extended transcript $\tr$ and either returns a valid witness or fails. +- a function $\pfail(\protocol, \alg{\prover}, e, \relation)$ returning a probability. + +We say that an accepting extended transcript $\tr$ contains "bad challenges" if and only if there exists a partial extended transcript $\tr'$, a challenge $c_i \in \badch(\tr')$, and some sequence of prover messages and challenges $([a_{i+1}], c_{i+1}, \ldots [a_j])$ such that $\tr = \tr' \,||\, (c_i, [a_{i+1}], c_{i+1}, \ldots [a_j])$. + +Theorem 1 requires that $e$, when given an accepting extended transcript $\tr$ that does not contain "bad challenges", returns a valid witness for that transcript except with probability bounded above by $\pfail(\protocol, \alg{\prover}, e, \relation)$. + +Our strategy is as follows: we will define $e$, establish an upper bound on $\pfail$ with respect to an adversary $\dlreladv$ that plays the $\dlrel_{\group,n+2}$ game, substitute these into Theorem 1, and then walk through the protocol to determine the upper bound of the size of $\badch(\tr')$. The adversary $\dlreladv$ plays the $\dlrel_{\group,n+2}$ game as follows: given the inputs $U, W \in \mathbb{G}, \mathbf{G} \in \mathbb{G}^n$, the adversary $\dlreladv$ simulates the game $\srwee_{\protocol, \relation}$ to $\alg{\prover}$ using the inputs from the $\dlrel_{\group,n+2}$ game as public parameters. If $\alg{\prover}$ manages to produce an accepting extended transcript $\tr$, $\dlreladv$ invokes a function $h$ on $\tr$ and returns its output. We shall define $h$ in such a way that for an accepting extended transcript $\tr$ that does not contain "bad challenges", $e(\tr)$ _always_ returns a valid witness whenever $h(\tr)$ does _not_ return a non-trivial discrete log relation. This means that the probability $\pfail(\protocol, \alg{\prover}, e, \relation)$ is no greater than $\adv^\dlrel_{\group,n+2}(\dlreladv, \sec)$, establishing our claim. + +#### Helpful substitutions + +We will perform some substitutions to aid in exposition. First, let us define the polynomial + +$$ +\kappa(X) = \prod_{j=0}^{k - 1} (1 + u_{k - 1 - j} X^{2^j}) +$$ + +so that we can write $\mathbf{b}_0 = \kappa(x_3)$. The coefficient vector $\mathbf{s}$ of $\kappa(X)$ is defined such that + +$$\mathbf{s}_i = \prod\limits_{j=0}^{k-1} u_{k - 1 - j}^{f(i, j)}$$ + +where $f(i, j)$ returns $1$ when the $j$th bit of $i$ is set, and $0$ otherwise. We can also write $\mathbf{G'}_0 = \innerprod{\mathbf{s}}{\mathbf{G}}$. + +### Description of function $h$ + +Recall that an accepting transcript $\tr$ is such that + +$$ +\sum_{i=0}^{k - 1} [u_j^{-1}] \rep{L_j} + \rep{P'} + \sum_{i=0}^{k - 1} [u_j] \rep{R_j} = [c] \mathbf{G'}_0 + [c z \mathbf{b}_0] U + [f] W +$$ + +By inspection of the representations of group elements with respect to $\mathbf{G}, U, W$ (recall that $\alg{\prover}$ is algebraic and so $\dlreladv$ has them), we obtain the $n$ equalities + +$$ +\sum_{i=0}^{k - 1} u_j^{-1} \repv{L_j}{G}{i} + \repv{P'}{G}{i} + \sum_{i=0}^{k - 1} u_j \repv{R_j}{G}{i} = c \mathbf{s}_i \forall i \in [0, n) +$$ + +and the equalities + +$$ +\sum_{i=0}^{k - 1} u_j^{-1} \repr{L_j}{U} + \repr{P'}{U} + \sum_{i=0}^{k - 1} u_j \repr{R_j}{U} = c z \kappa(x_3) +$$ + +$$ +\sum_{i=0}^{k - 1} u_j^{-1} \repr{L_j}{W} + \repr{P'}{W} + \sum_{i=0}^{k - 1} u_j \repr{R_j}{W} = f +$$ + +We define the linear-time function $h$ that returns the representation of + +$$ +\begin{array}{rll} +\sum\limits_{i=0}^{n - 1} &\left[ \sum_{i=0}^{k - 1} u_j^{-1} \repv{L_j}{G}{i} + \repv{P'}{G}{i} + \sum_{i=0}^{k - 1} u_j \repv{R_j}{G}{i} - c \mathbf{s}_i \right] & \mathbf{G}_i \\[1ex] ++ &\left[ \sum_{i=0}^{k - 1} u_j^{-1} \repr{L_j}{U} + \repr{P'}{U} + \sum_{i=0}^{k - 1} u_j \repr{R_j}{U} - c z \kappa(x_3) \right] & U \\[1ex] ++ &\left[ \sum_{i=0}^{k - 1} u_j^{-1} \repr{L_j}{W} + \repr{P'}{W} + \sum_{i=0}^{k - 1} u_j \repr{R_j}{W} - f \right] & W +\end{array} +$$ + +which is always a discrete log relation. If any of the equalities above are not satisfied, then this discrete log relation is non-trivial. This is the function invoked by $\dlreladv$. + +#### The extractor function $e$ + +The extractor function $e$ simply returns $a_i(X)$ from the representation $\rep{A_i}$ for $i \in [0, n_a)$. Due to the restrictions we will place on the space of bad challenges in each round, we are guaranteed to obtain polynomials such that $g(X, C_0, C_1, \cdots, a_0(X), a_1(X), \cdots)$ vanishes over $D$ whenever the discrete log relation returned by the adversary's function $h$ is trivial. This trivially gives us that the extractor function $e$ succeeds with probability bounded above by $\pfail$ as required. + +#### Defining $\badch(\tr')$ + +Recall from before that the following $n$ equalities hold: + +$$ +\sum_{i=0}^{k - 1} u_j^{-1} \repv{L_j}{G}{i} + \repv{P'}{G}{i} + \sum_{i=0}^{k - 1} u_j \repv{R_j}{G}{i} = c \mathbf{s}_i \forall i \in [0, n) +$$ + +as well as the equality + +$$ +\sum_{i=0}^{k - 1} u_j^{-1} \repr{L_j}{U} + \repr{P'}{U} + \sum_{i=0}^{k - 1} u_j \repr{R_j}{U} = c z \kappa(x_3) +$$ + +For convenience let us introduce the following notation + +$$ +\begin{array}{ll} +\mv{G}{i}{m} &= \sum_{i=0}^{m - 1} u_j^{-1} \repv{L_j}{G}{i} + \repv{P'}{G}{i} + \sum_{i=0}^{m - 1} u_j \repv{R_j}{G}{i} \\[1ex] +\m{U}{m} &= \sum_{i=0}^{m - 1} u_j^{-1} \repr{L_j}{U} + \repr{P'}{U} + \sum_{i=0}^{m - 1} u_j \repr{R_j}{U} +\end{array} +$$ + +so that we can rewrite the above (after expanding for $\kappa(x_3)$) as + +$$ +\mv{G}{i}{k} = c \mathbf{s}_i \forall i \in [0, n) +$$ + +$$ +\m{U}{k} = c z \prod_{j=0}^{k - 1} (1 + u_{k - 1 - j} x_3^{2^j}) +$$ + +We can combine these equations by multiplying both sides of each instance of the first equation by $\mathbf{s}_i^{-1}$ (because $\mathbf{s}_i$ is never zero) and substituting for $c$ in the second equation, yielding the following $n$ equalities: + +$$ +\m{U}{k} = \mv{G}{i}{k} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{k - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \forall i \in [0, n) +$$ + +> **Lemma 1.** If $\m{U}{k} = \mv{G}{i}{k} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{k - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \forall i \in [0, n)$ then it follows that $\repr{P'}{U} = z \sum\limits_{i=0}^{2^k - 1} x_3^i \repv{P'}{G}{i}$ for all transcripts that do not contain bad challenges. +> +> _Proof._ It will be useful to introduce yet another abstraction defined starting with +> $$ +\z{k}{m}{i} = \mv{G}{i}{m} +$$ +> and then recursively defined for all integers $r$ such that $0 \lt r \leq k$ +> $$ +\z{k - r}{m}{i} = \z{k - r + 1}{m}{i} + x_3^{2^{k - r}} \z{k - r + 1}{m}{i + 2^{k - r}} +$$ +> This allows us to rewrite our above equalities as +> $$ +\m{U}{k} = \z{k}{k}{i} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{k - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \forall i \in [0, n) +$$ +> +> We will now show that for all integers $r$ such that $0 \lt r \leq k$ that whenever the following holds for $r$ +> $$ +\m{U}{r} = \z{r}{r}{i} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \forall i \in [0, 2^r) +$$ +> that the same _also_ holds for +> $$ +\m{U}{r - 1} = \z{r - 1}{r - 1}{i} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 2 - j} x_3^{2^j}) \forall i \in [0, 2^{r-1}) +$$ +> +> For all integers $r$ such that $0 \lt r \leq k$ we have that $\mathbf{s}_{i + 2^{r - 1}} = u_{r - 1} \mathbf{s}_i \forall i \in [0, 2^{r - 1})$ by the definition of $\mathbf{s}$. This gives us $\mathbf{s}_{i+2^{r - 1}}^{-1} = \mathbf{s}_i^{-1} u_{r - 1}^{-1} \forall i \in [0, 2^{r - 1})$ as no value in $\mathbf{s}$ nor any challenge $u_r$ are zeroes. We can use this to relate one half of the equalities with the other half as so: +> $$ +\begin{array}{rl} +\m{U}{r} &= \z{r}{r}{i} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \\ +&= \z{r}{r}{i + 2^{r - 1}} \cdot \mathbf{s}_i^{-1} u_{r - 1}^{-1} z \prod_{j=0}^{r - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \\ +&\forall i \in [0, 2^{r - 1}) +\end{array} +$$ +> +> Notice that $\z{r}{r}{i}$ can be rewritten as $u_{r - 1}^{-1} \repv{L_{r - 1}}{G}{i} + \z{r}{r - 1}{i} + u_{r - 1} \repv{R_{r - 1}}{G}{i}$ for all $i \in [0, 2^{r})$. Thus we can rewrite the above as +> +> $$ +\begin{array}{rl} +\m{U}{r} &= \left( u_{r - 1}^{-1} \repv{L_{r - 1}}{G}{i} + \z{r}{r - 1}{i} + u_{r - 1} \repv{R_{r - 1}}{G}{i} \right) \\ +&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \\ +&= \left( u_{r - 1}^{-1} \repv{L_{r - 1}}{G}{i + 2^{r - 1}} + \z{r}{r - 1}{i + 2^{r - 1}} + u_{r - 1} \repv{R_{r - 1}}{G}{i + 2^{r - 1}} \right) \\ +&\cdot \; \mathbf{s}_i^{-1} u_{r - 1}^{-1} z \prod_{j=0}^{r - 1} (1 + u_{k - 1 - j} x_3^{2^j}) \\ +&\forall i \in [0, 2^{r - 1}) +\end{array} +$$ +> +> Now let us rewrite these equalities substituting $u_{r - 1}$ with formal indeterminate $X$. +> +> $$ +\begin{array}{rl} +& X^{-1} \repr{L_{r - 1}}{U} + \m{U}{r - 1} + X \repr{R_{r - 1}}{U} \\ +&= \left( X^{-1} \repv{L_{r - 1}}{G}{i} + \z{r}{r - 1}{i} + X \repv{R_{r - 1}}{G}{i} \right) \\ +&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) (1 + x_3^{2^{r - 1}} X) \\ +&= \left( X^{-1} \repv{L_{r - 1}}{G}{i + 2^{r - 1}} + \z{r}{r - 1}{i + 2^{r - 1}} + X \repv{R_{r - 1}}{G}{i + 2^{r - 1}} \right) \\ +&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) (X^{-1} + x_3^{2^{r - 1}}) \\ +&\forall i \in [0, 2^{r - 1}) +\end{array} +$$ +> +> Now let us rescale everything by $X^2$ to remove negative exponents. +> +> $$ +\begin{array}{rl} +& X \repr{L_{r - 1}}{U} + X^2 \m{U}{r - 1} + X^3 \repr{R_{r - 1}}{U} \\ +&= \left( X^{-1} \repv{L_{r - 1}}{G}{i} + \z{r}{r - 1}{i} + X \repv{R_{r - 1}}{G}{i} \right) \\ +&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) (X^2 + x_3^{2^{r - 1}} X^3) \\ +&= \left( X^{-1} \repv{L_{r - 1}}{G}{i + 2^{r - 1}} + \z{r}{r - 1}{i + 2^{r - 1}} + X \repv{R_{r - 1}}{G}{i + 2^{r - 1}} \right) \\ +&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) (X + x_3^{2^{r - 1}} X^2) \\ +&\forall i \in [0, 2^{r - 1}) +\end{array} +$$ +> +> This gives us $2^{r - 1}$ triples of maximal degree-$4$ polynomials in $X$ that agree at $u_{r - 1}$ despite having coefficients determined prior to the choice of $u_{r - 1}$. The probability that two of these polynomials would agree at $u_{r - 1}$ and yet be distinct would be $\frac{4}{|\ch|}$ by the Schwartz-Zippel lemma and so by the union bound the probability that the three of these polynomials agree and yet any of them is distinct from another is $\frac{8}{|\ch|}$. By the union bound again the probability that any of the $2^{r - 1}$ triples have multiple distinct polynomials is $\frac{2^{r - 1}\cdot8}{|\ch|}$. By restricting the challenge space for $u_{r - 1}$ accordingly we obtain $|\badch(\trprefix{\tr'}{u_r})|/|\ch| \leq \frac{2^{r - 1}\cdot8}{|\ch|}$ for integers $0 \lt r \leq k$ and thus $|\badch(\trprefix{\tr'}{u_k})|/|\ch| \leq \frac{4n}{|\ch|} \leq \epsilon$. +> +> We can now conclude an equality of polynomials, and thus of coefficients. Consider the coefficients of the constant terms first, which gives us the $2^{r - 1}$ equalities +> $$ +0 = 0 = \mathbf{s}_i^{-1} z \left( \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) \right) \cdot \repv{L_{r - 1}}{G}{i + 2^{r - 1}} \forall i \in [0, 2^{r - 1}) +$$ +> +> No value of $\mathbf{s}$ is zero, $z$ is never chosen to be $0$ and each $u_j$ is chosen so that $1 + u_{k - 1 - j} x_3^{2^j}$ is nonzero, so we can then conclude +> $$ +0 = \repv{L_{r - 1}}{G}{i + 2^{r - 1}} \forall i \in [0, 2^{r - 1}) +$$ +> +> An identical process can be followed with respect to the coefficients of the $X^4$ term in the equalities to establish $0 = \repv{R_{r - 1}}{G}{i} \forall i \in [0, 2^{r - 1})$ contingent on $x_3$ being nonzero, which it always is. Substituting these in our equalities yields us something simpler +> +> $$ +\begin{array}{rl} +& X \repr{L_{r - 1}}{U} + X^2 \m{U}{r - 1} + X^3 \repr{R_{r - 1}}{U} \\ +&= \left( X^{-1} \repv{L_{r - 1}}{G}{i} + \z{r}{r - 1}{i} \right) \\ +&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) (X^2 + x_3^{2^{r - 1}} X^3) \\ +&= \left( \z{r}{r - 1}{i + 2^{r - 1}} + X \repv{R_{r - 1}}{G}{i + 2^{r - 1}} \right) \\ +&\cdot \; \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) (X + x_3^{2^{r - 1}} X^2) \\ +&\forall i \in [0, 2^{r - 1}) +\end{array} +$$ +> +> Now we will consider the coefficients in $X$, which yield the equalities +> +> $$ +\begin{array}{rl} +\repr{L_{r - 1}}{U} &= \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) \cdot \repv{L_{r - 1}}{G}{i} \\ +&= \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) \cdot \z{r}{r - 1}{i + 2^{r - 1}} \\ +&\forall i \in [0, 2^{r - 1}) +\end{array} +$$ +> +> which for similar reasoning as before yields the equalities +> $$ +\repv{L_{r - 1}}{G}{i} = \z{r}{r - 1}{i + 2^{r - 1}} \forall i \in [0, 2^{r - 1}) +$$ +> +> Finally we will consider the coefficients in $X^2$ which yield the equalities +> +> $$ +\begin{array}{rl} +\m{U}{r - 1} &= \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) \cdot \left( \z{r}{r - 1}{i} + \repv{L_{r - 1}}{G}{i} x_3^{2^{r - 1}} \right) \\ +&\forall i \in [0, 2^{r - 1}) +\end{array} +$$ +> +> which by substitution gives us $\forall i \in [0, 2^{r - 1})$ +> $$ +\m{U}{r - 1} = \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) \cdot \left( \z{r}{r - 1}{i} + \z{r}{r - 1}{i + 2^{r - 1}} x_3^{2^{r - 1}} \right) +$$ +> +> Notice that by the definition of $\z{r - 1}{m}{i}$ we can rewrite this as +> +> $$ +\m{U}{r - 1} = \z{r - 1}{r - 1}{i} \cdot \mathbf{s}_i^{-1} z \prod_{j=0}^{r - 2} (1 + u_{k - 1 - j} x_3^{2^j}) \forall i \in [0, 2^{r - 1}) +$$ +> +> which is precisely in the form we set out to demonstrate. +> +> We now proceed by induction from the case $r = k$ (which we know holds) to reach $r = 0$, which gives us +$$ +\m{U}{0} = \z{0}{0}{0} \cdot \mathbf{s}_0^{-1} z +$$ +> +> and because $\m{U}{0} = \repr{P'}{U}$ and $\z{0}{0}{0} = \sum_{i=0}^{2^k - 1} x_3^i \repv{P'}{G}{i}$, we obtain $\repr{P'}{U} = z \sum_{i=0}^{2^k - 1} x_3^i \repv{P'}{G}{i}$, which completes the proof. + +Having established that $\repr{P'}{U} = z \sum_{i=0}^{2^k - 1} x_3^i \repv{P'}{G}{i}$, and given that $x_3$ and $\repv{P'}{G}{i}$ are fixed in advance of the choice of $z$, we have that at most one value of $z \in \ch$ (which is nonzero) exists such that $\repr{P'}{U} = z \sum_{i=0}^{2^k - 1} x_3^i \repv{P'}{G}{i}$ and yet $\repr{P'}{U} \neq 0$. By restricting $|\badch(\trprefix{\tr'}{z})|/|\ch| \leq \frac{1}{|\ch|} \leq \epsilon$ accordingly we obtain $\repr{P'}{U} = 0$ and therefore that the polynomial defined by $\repr{P'}{\mathbf{G}}$ has a root at $x_3$. + +By construction $P' = P - [v] \mathbf{G}_0 + [\xi] S$, giving us that the polynomial defined by $\repr{P + [\xi] S}{\mathbf{G}}$ evaluates to $v$ at the point $x_3$. We have that $v, P, S$ are fixed prior to the choice of $\xi$, and so either the polynomial defined by $\repr{S}{\mathbf{G}}$ has a root at $x_3$ (which implies the polynomial defined by $\repr{P}{\mathbf{G}}$ evaluates to $v$ at the point $x_3$) or else $\xi$ is the single solution in $\ch$ for which $\repr{P + [\xi] S}{\mathbf{G}}$ evaluates to $v$ at the point $x_3$ while $\repr{P}{\mathbf{G}}$ itself does not. We avoid the latter case by restricting $|\badch(\trprefix{\tr'}{\xi})|/|\ch| \leq \frac{1}{|\ch|} \leq \epsilon$ accordingly and can thus conclude that the polynomial defined by $\repr{P}{\mathbf{G}}$ evaluates to $v$ at $x_3$. + +The remaining work deals strictly with the representations of group elements sent previously by the prover and their relationship with $P$ as well as the challenges chosen in each round of the protocol. We will simplify things first by using $p(X)$ to represent the polynomial defined by $\repr{P}{\mathbf{G}}$, as it is the case that this $p(X)$ corresponds exactly with the like-named polynomial in the protocol itself. We will make similar substitutions for the other group elements (and their corresponding polynomials) to aid in exposition, as the remainder of this proof is mainly tedious application of the Schwartz-Zippel lemma to upper bound the bad challenge space size for each of the remaining challenges in the protocol. + +Recall that $P = Q' + x_4 \sum\limits_{i=0}^{n_q - 1} [x_4^i] Q_i$, and so by substitution we have $p(X) = q'(X) + x_4 \sum\limits_{i=0}^{n_q - 1} x_4^i q_i(X)$. Recall also that + +$$ +v = \sum\limits_{i=0}^{n_q - 1} +\left( +x_2^i + \left( + \frac + { \mathbf{u}_i - r_i(x_3) } + {\prod\limits_{j=0}^{n_e - 1} + \left( + x_3 - \omega^{\left( + \mathbf{q_i} + \right)_j} x + \right) + } + \right) +\right) ++ +x_4 \sum\limits_{i=0}^{n_q - 1} x_4 \mathbf{u}_i +$$ + +We have already established that $p(x_3) = v$. Notice that the coefficients in the above expressions for $v$ and $P$ are fixed prior to the choice of $x_4 \in \ch$. By the Schwartz-Zippel lemma we have that only at most $n_q + 1$ possible choices of $x_4$ exist such that these expressions are satisfied and yet $q_i(x_3) \neq \mathbf{u}_i$ for any $i$ or + +$$ +q'(x_3) \neq \sum\limits_{i=0}^{n_q - 1} +\left( +x_2^i + \left( + \frac + { \mathbf{u}_i - r_i(x_3) } + {\prod\limits_{j=0}^{n_e - 1} + \left( + x_3 - \omega^{\left( + \mathbf{q_i} + \right)_j} x + \right) + } + \right) +\right) +$$ + +By restricting $|\badch(\trprefix{\tr'}{x_4})|/|\ch| \leq \frac{n_q + 1}{|\ch|} \leq \epsilon$ we can conclude that all of the aforementioned inequalities are untrue. Now we can substitute $\mathbf{u}_i$ with $q_i(x_3)$ for all $i$ to obtain + +$$ +q'(x_3) = \sum\limits_{i=0}^{n_q - 1} +\left( +x_2^i + \left( + \frac + { q_i(x_3) - r_i(x_3) } + {\prod\limits_{j=0}^{n_e - 1} + \left( + x_3 - \omega^{\left( + \mathbf{q_i} + \right)_j} x + \right) + } + \right) +\right) +$$ + +Suppose that $q'(X)$ (which is the polynomial defined by $\repr{Q'}{\mathbf{G}}$, and is of degree at most $n - 1$) does _not_ take the form + +$$\sum\limits_{i=0}^{n_q - 1} + +x_2^i + \left( + \frac + {q_i(X) - r_i(X)} + {\prod\limits_{j=0}^{n_e - 1} + \left( + X - \omega^{\left( + \mathbf{q_i} + \right)_j} x + \right) + } + \right) +$$ + +and yet $q'(X)$ agrees with this expression at $x_3$ as we've established above. By the Schwartz-Zippel lemma this can only happen for at most $n - 1$ choices of $x_3 \in \ch$ and so by restricting $|\badch(\trprefix{\tr'}{x_3})|/|\ch| \leq \frac{n - 1}{|\ch|} \leq \epsilon$ we obtain that + +$$q'(X) = \sum\limits_{i=0}^{n_q - 1} + +x_2^i + \left( + \frac + {q_i(X) - r_i(X)} + {\prod\limits_{j=0}^{n_e - 1} + \left( + X - \omega^{\left( + \mathbf{q_i} + \right)_j} x + \right) + } + \right) +$$ + +Next we will extract the coefficients of this polynomial in $x_2$ (which are themselves polynomials in formal indeterminate $X$) by again applying the Schwartz-Zippel lemma with respect to $x_2$; again, this leads to the restriction $|\badch(\trprefix{\tr'}{x_2})|/|\ch| \leq \frac{n_q}{|\ch|} \leq \epsilon$ and we obtain the following polynomials of degree at most $n - 1$ for all $i \in [0, n_q - 1)$ + +$$ +\frac + {q_i(X) - r_i(X)} + {\prod\limits_{j=0}^{n_e - 1} + \left( + X - \omega^{\left( + \mathbf{q_i} + \right)_j} x + \right) + } +$$ + +Having established that these are each non-rational polynomials of degree at most $n - 1$ we can then say (by the factor theorem) that for each $i \in [0, n_q - 1]$ and $j \in [0, n_e - 1]$ we have that $q_i(X) - r_i(X)$ has a root at $\omega^{\left(\mathbf{q_i}\right)_j} x$. Note that we can interpret each $q_i(X)$ as the restriction of a _bivariate_ polynomial at the point $x_1$ whose degree with respect to $x_1$ is at most $n_a + 1$ and whose coefficients consist of various polynomials $a'_i(X)$ (from the representation $\repr{A'_i}{\mathbf{G}}$) as well as $h'(X)$ (from the representation $\repr{H'_i}{\mathbf{G}}$) and $r(X)$ (from the representation $\repr{R}{\mathbf{G}}$). By similarly applying the Schwartz-Zippel lemma and restricting the challenge space with $|\badch(\trprefix{\tr'}{x_1})|/|\ch| \leq \frac{n_a + 1}{|\ch|} \leq \epsilon$ we obtain (by construction of each $q'_i(X)$ and $r_i(X)$ in steps 12 and 13 of the protocol) that the prover's claimed value of $r$ in step 9 is equal to $r(x)$; that the value $h$ computed by the verifier in step 13 is equal to $h'(x)$; and that for all $i \in [0, n_q - 1]$ the prover's claimed values $(\mathbf{a_i})_j = a'_i(\omega^{(\mathbf{p_i})_j} x)$ for all $j \in [0, n_e - 1]$. + +By construction of $h'(X)$ (from the representation $\repr{H'}{\mathbf{G}}$) in step 7 we know that $h'(x) = h(x)$ where by $h(X)$ we refer to the polynomial of degree at most $(n_g - 1) \cdot (n - 1)$ whose coefficients correspond to the concatenated representations of each $\repr{H_i}{\mathbf{G}}$. As before, suppose that $h(X)$ does _not_ take the form $g'(X) / t(X)$. Then because $h(X)$ is determined prior to the choice of $x$ then by the Schwartz-Zippel lemma we know that it would only agree with $g'(X) / t(X)$ at $(n_g - 1) \cdot (n - 1)$ points at most if the polynomials were not equal. By restricting again $|\badch(\trprefix{\tr'}{x})|/|\ch| \leq \frac{(n_g - 1) \cdot (n - 1)}{|\ch|} \leq \epsilon$ we obtain $h(X) = g'(X) / t(X)$ and because $h(X)$ is a non-rational polynomial by the factor theorem we obtain that $g'(X)$ vanishes over the domain $D$. + +We now have that $g'(X)$ vanishes over $D$ but wish to show that $g(X, C_0, C_1, \cdots)$ vanishes over $D$ at all points to complete the proof. This just involves a sequence of applying the same technique to each of the challenges; since the polynomial $g(\cdots)$ has degree at most $n_g \cdot (n - 1)$ in any indeterminate by definition, and because each polynomial $a_i(X, C_0, C_1, ..., C_{i - 1}, \cdots)$ is determined prior to the choice of concrete challenge $c_i$ by similarly bounding $|\badch(\trprefix{\tr'}{c_i})|/|\ch| \leq \frac{n_g \cdot (n - 1)}{|\ch|} \leq \epsilon$ we ensure that $g(X, C_0, C_1, \cdots)$ vanishes over $D$, completing the proof. diff --git a/book/src/design/proving-system/circuit-commitments.md b/book/src/design/proving-system/circuit-commitments.md index 2c5d4abd56..7352788929 100644 --- a/book/src/design/proving-system/circuit-commitments.md +++ b/book/src/design/proving-system/circuit-commitments.md @@ -61,7 +61,7 @@ Let $c$ be the number of columns that are enabled for equality constraints. Let $m$ be the maximum number of columns that can accommodated by a [column set](permutation.md#spanning-a-large-number-of-columns) without exceeding -the PLONK configuration's polynomial degree bound. +the PLONK configuration's maximum constraint degree. Let $u$ be the number of “usable” rows as defined in the [Permutation argument](permutation.md#zero-knowledge-adjustment) section. diff --git a/book/src/design/proving-system/comparison.md b/book/src/design/proving-system/comparison.md index 1691fe743b..87496a82b0 100644 --- a/book/src/design/proving-system/comparison.md +++ b/book/src/design/proving-system/comparison.md @@ -26,6 +26,7 @@ equivalent objects in Halo 2 (which builds on the nomenclature from the Halo pap | $\bar{\omega}$ | `s_poly_blind` | | $\bar{C}$ | `s_poly_commitment` | | $h(X)$ | $g(X)$ | +| $U$ | $G$ | | $\omega'$ | `blind` / $\xi$ | | $\mathbf{c}$ | $\mathbf{a}$ | | $c$ | $a = \mathbf{a}_0$ | @@ -47,7 +48,7 @@ Halo 2's polynomial commitment scheme differs from Appendix A.2 of BCMS20 in two sampling $z$. 2. The $\text{PC}_\text{DL}.\text{SuccinctCheck}$ subroutine (Figure 2 of BCMS20) computes - the initial group element $C_0$ by adding $[v] H' = [v \epsilon] H$, which requires two + the initial group element $C_0$ by adding $[v] H' = [v \xi_0] H$, which requires two scalar multiplications. Instead, we subtract $[v] G_0$ from the original commitment $P$, so that we're effectively opening the polynomial at the point to the value zero. The computation $[v] G_0$ is more efficient in the context of recursion because $G_0$ is a diff --git a/book/src/design/proving-system/lookup.md b/book/src/design/proving-system/lookup.md index 0fcf240de0..3bc7a4a99d 100644 --- a/book/src/design/proving-system/lookup.md +++ b/book/src/design/proving-system/lookup.md @@ -1,11 +1,11 @@ # Lookup argument -halo2 uses the following lookup technique, which allows for lookups in arbitrary sets, and +Halo 2 uses the following lookup technique, which allows for lookups in arbitrary sets, and is arguably simpler than Plookup. ## Note on Language -In addition to the [general notes on language](../design.md#note-on-language): +In addition to the [general notes on language](../../design.md#note-on-language): - We call the $Z(X)$ polynomial (the grand product argument polynomial for the permutation argument) the "permutation product" column. @@ -147,7 +147,7 @@ soundness is not affected. ## Generalizations -halo2's lookup argument implementation generalizes the above technique in the following +Halo 2's lookup argument implementation generalizes the above technique in the following ways: - $A$ and $S$ can be extended to multiple columns, combined using a random challenge. $A'$ diff --git a/book/src/design/proving-system/multipoint-opening.md b/book/src/design/proving-system/multipoint-opening.md index 6b7742ae8d..98982928c0 100644 --- a/book/src/design/proving-system/multipoint-opening.md +++ b/book/src/design/proving-system/multipoint-opening.md @@ -55,7 +55,8 @@ The multipoint opening optimization proceeds as such: ] ``` NB: `q_eval_sets` is a vector of sets of evaluations, where the outer vector - goes over the point sets, and the inner vector goes over the points in each set. + corresponds to the point sets, which in this example are $\{x\}$ and $\{x, \omega x\}$, + and the inner vector corresponds to the points in each set. 3. Interpolate each set of values in `q_eval_sets`: `r_polys`: $$ diff --git a/book/src/design/proving-system/permutation-diagram.png b/book/src/design/proving-system/permutation-diagram.png index 42e2356795..a0f683a90d 100644 Binary files a/book/src/design/proving-system/permutation-diagram.png and b/book/src/design/proving-system/permutation-diagram.png differ diff --git a/book/src/design/proving-system/permutation-diagram.svg b/book/src/design/proving-system/permutation-diagram.svg index 5ef826221b..2ae7f31461 100644 --- a/book/src/design/proving-system/permutation-diagram.svg +++ b/book/src/design/proving-system/permutation-diagram.svg @@ -19,6 +19,105 @@ inkscape:export-ydpi="150"> + + + + + + + + + + + + + + + + + + + + + @@ -182,36 +281,6 @@ d="M 0,0 5,-5 -12.5,0 5,5 Z" id="path1450-2" /> - - - - - - - - - - - - + id="path964-3-6-8" /> + id="path2832-6-8" /> + inkscape:isstock="true" + inkscape:collect="always"> + id="path1450-5-6" /> + inkscape:isstock="true"> + id="path2744-4-4-9" /> + inkscape:isstock="true"> + id="path964-3-6-1-5" /> + id="path2832-6-9-0" /> + + + + + + + + + + id="path958-6-3-60" /> + + + + + + + + + + + + + id="path964-5-38-2-5" /> - - - - - - image/svg+xml - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + 7 + + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05556px;line-height:1.25;font-family:Cabin;-inkscape-font-specification:Cabin;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;white-space:pre;shape-inside:url(#rect8304);shape-padding:0;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;" /> + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05556px;line-height:1.25;font-family:Cabin;-inkscape-font-specification:Cabin;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;white-space:pre;shape-inside:url(#rect8314);shape-padding:0;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;" /> - A - B - C - D - - - 7 + 7 + 7 - - - - + id="g1861"> B C - D - A - + id="tspan835-6-6-8" + x="76.521111" + y="22.629126" + style="stroke-width:0.264583">7 + + + C + 7 + - 7 - 7 + id="tspan843-4-8" + x="62.274574" + y="48.880329" + style="stroke-width:0.264583">D 7 - 7 + + + + A + 7 - A - B - C - D - - - - 7 - 3 7 + 7 - - - + y="35.795494" + style="stroke-width:0.264583">3 + id="text2282-7-9-8" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.05556px;line-height:1.25;font-family:MathJax_Math;-inkscape-font-specification:MathJax_Math;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:0px;word-spacing:0px;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.465;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill" + d="m 160.71124,37.647857 c -0.431,0.38128 -0.64145,1.226314 -1.34375,1.037109 -0.74284,0 -1.48568,0 -2.22852,0 -0.15916,0.716062 0.0965,1.017356 0.83086,0.839844 0.49782,0.0496 1.68661,-0.229516 0.82771,0.465521 -0.45759,0.233217 -1.12169,0.03237 -1.65857,0.09893 -0.15655,0.716056 0.0901,1.028927 0.83086,0.847656 -0.33633,0.341887 -0.59275,0.820453 0.15649,1.112399 0.48187,-0.305335 0.70166,-1.318573 1.4521,-1.112399 0.74675,0 1.49349,0 2.24024,0 0.15655,-0.716056 -0.0901,-1.028927 -0.83086,-0.847656 -0.49901,-0.05027 -1.69333,0.23023 -0.83553,-0.46552 0.46012,-0.233399 1.12703,-0.03212 1.66639,-0.09893 0.15916,-0.716061 -0.0965,-1.017356 -0.83086,-0.839844 0.27758,-0.32261 0.61099,-0.834495 -0.17076,-1.119474 -0.0452,-0.06574 -0.0705,0.05491 -0.1058,0.08237 z" /> + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:MathJax_Math;-inkscape-font-specification:MathJax_Math;stroke-width:0.264583">≠ + B + σ + id="tspan835-6-6-8-9" + x="187.47472" + y="22.629129" + style="stroke-width:0.264583">7 + id="g1868"> B - C - D A - - + id="tspan843-2-50-0-5" + x="187.47475" + y="48.961861" + style="stroke-width:0.264583">7 + + A + 7 + + + 3 7 - C + + + + + + + + + + + + + σ + + + + + + + + + + + 7 + style="stroke-width:0.264583">7 + C + B + A + D + + + + + + + + + + + + σ + + + + + + + + + A + B + C + σ + id="tspan847-20" + x="14.526597" + y="62.0467" + style="stroke-width:0.264583">D diff --git a/book/src/design/proving-system/vanishing.md b/book/src/design/proving-system/vanishing.md index 60025da7e7..72e1e21ee3 100644 --- a/book/src/design/proving-system/vanishing.md +++ b/book/src/design/proving-system/vanishing.md @@ -41,7 +41,7 @@ verifier samples $y$) linear combination of the circuit relations. ## Committing to $h(X)$ -$h(X)$ has degree $(d - 1)n - d$ (because the divisor $t(X)$ has degree $n$). However, the +$h(X)$ has degree $d(n - 1) - n$ (because the divisor $t(X)$ has degree $n$). However, the polynomial commitment scheme we use for Halo 2 only supports committing to polynomials of degree $n - 1$ (which is the maximum degree that the rest of the protocol needs to commit to). Instead of increasing the cost of the polynomial commitment scheme, the prover split diff --git a/book/src/user/tips-and-tricks.md b/book/src/user/tips-and-tricks.md index f4c880acb0..b2d7be0d95 100644 --- a/book/src/user/tips-and-tricks.md +++ b/book/src/user/tips-and-tricks.md @@ -14,7 +14,7 @@ the form: $$a \cdot (1 - a) \cdot (2 - a) \cdot (3 - a) \cdot (4 - a) = 0$$ -while to constraint $c$ to be either 7 or 13, you would use: +while to constrain $c$ to be either 7 or 13, you would use: $$(7 - c) \cdot (13 - c) = 0$$ diff --git a/halo2/CHANGELOG.md b/halo2/CHANGELOG.md index 62f528e75e..19cfec4d18 100644 --- a/halo2/CHANGELOG.md +++ b/halo2/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [0.1.0-beta.2] - 2022-02-14 ### Removed - Everything (moved to `halo2_proofs` crate). diff --git a/halo2/Cargo.toml b/halo2/Cargo.toml index 68a660a6e8..7a6bbaa94f 100644 --- a/halo2/Cargo.toml +++ b/halo2/Cargo.toml @@ -1,21 +1,25 @@ [package] name = "halo2" -version = "0.1.0-beta.1" +version = "0.1.0-beta.2" authors = [ - "Sean Bowe ", - "Ying Tong Lai ", - "Daira Hopwood ", "Jack Grigg ", ] -edition = "2018" -license-file = "../COPYING" +edition = "2021" +rust-version = "1.56.1" +description = "[BETA] Fast zero-knowledge proof-carrying data implementation with no trusted setup" +license = "MIT OR Apache-2.0" repository = "https://github.com/zcash/halo2" documentation = "https://docs.rs/halo2" readme = "../README.md" +categories = ["cryptography"] +keywords = ["halo", "proofs", "recursive", "zkp", "zkSNARKs"] [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "../katex-header.html"] +rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] [dependencies] -halo2_proofs = { version = "0.1.0-beta.1", path = "../halo2_proofs" } +halo2_proofs = { version = "0.2", path = "../halo2_proofs" } + +[lib] +bench = false diff --git a/katex-header.html b/halo2/katex-header.html similarity index 100% rename from katex-header.html rename to halo2/katex-header.html diff --git a/halo2_gadgets/CHANGELOG.md b/halo2_gadgets/CHANGELOG.md index df364a5cc8..41019f9cea 100644 --- a/halo2_gadgets/CHANGELOG.md +++ b/halo2_gadgets/CHANGELOG.md @@ -7,10 +7,115 @@ and this project adheres to Rust's notion of ## [Unreleased] +## [0.2.0] - 2022-06-23 +### Added +- `halo2_gadgets::utilities::RangeConstrained>::bitrange_of` + +### Changed +All APIs that represented witnessed values as `Option` now represent them as +`halo2_proofs::circuit::Value`. The core API changes are listed below. + +- Migrated to `halo2_proofs 0.2.0`. +- The following APIs now take `Value<_>` instead of `Option<_>`: + - `halo2_gadgets::ecc`: + - `EccInstructions::{witness_point, witness_point_non_id}` + - `EccInstructions::{witness_scalar_var, witness_scalar_fixed}` + - `ScalarVar::new` + - `ScalarFixed::new` + - `NonIdentityPoint::new` + - `Point::new` + - `halo2_gadgets::sinsemilla`: + - `SinsemillaInstructions::witness_message_piece` + - `MessagePiece::{from_field_elem, from_subpieces}` + - `halo2_gadgets::sinsemilla::merkle`: + - `MerklePath::construct` + - `halo2_gadgets::utilities`: + - `UtilitiesInstructions::load_private` + - `RangeConstrained::witness_short` + - `halo2_gadgets::utilities::cond_swap`: + - `CondSwapInstructions::swap` + - `halo2_gadgets::utilities::decompose_running_sum`: + - `RunningSumConfig::witness_decompose` + - `halo2_gadgets::utilities::lookup_range_check`: + - `LookupRangeCheckConfig::{witness_check, witness_short_check}` +- The following APIs now return `Value<_>` instead of `Option<_>`: + - `halo2_gadgets::ecc::chip`: + - `EccPoint::{point, is_identity}` + - `NonIdentityEccPoint::point` + - `halo2_gadgets::utilities`: + - `FieldValue::value` + - `Var::value` + - `RangeConstrained::value` +- `halo2_gadgets::sha256::BlockWord` is now a newtype wrapper around + `Value` instead of `Option`. + +### Removed +- `halo2_gadgets::utilities::RangeConstrained>::bitrange_of` + +## [0.1.0] - 2022-05-10 +### Added +- `halo2_gadgets::utilities`: + - `FieldValue` trait. + - `RangeConstrained` newtype wrapper. +- `halo2_gadgets::ecc`: + - `EccInstructions::witness_scalar_var` API to witness a full-width scalar + used in variable-base scalar multiplication. + - `EccInstructions::witness_scalar_fixed`, to witness a full-width scalar + used in fixed-base scalar multiplication. + - `EccInstructions::scalar_fixed_from_signed_short`, to construct a signed + short scalar used in fixed-base scalar multiplication from its magnitude and + sign. + - `BaseFitsInScalarInstructions` trait that can be implemented for a curve + whose base field fits into its scalar field. This provides a method + `scalar_var_from_base` that converts a base field element that exists as + a variable in the circuit, into a scalar to be used in variable-base + scalar multiplication. + - `ScalarFixed::new` + - `ScalarFixedShort::new` + - `ScalarVar::new` and `ScalarVar::from_base` gadget APIs. +- `halo2_gadgets::ecc::chip`: + - `ScalarVar` enum with `BaseFieldElem` and `FullWidth` variants. `FullWidth` + is unimplemented for `halo2_gadgets v0.1.0`. +- `halo2_gadgets::poseidon`: + - `primitives` (moved from `halo2_gadgets::primitives::poseidon`) +- `halo2_gadgets::sinsemilla`: + - `primitives` (moved from `halo2_gadgets::primitives::sinsemilla`) + - `MessagePiece::from_subpieces` + ### Changed -- pass in an additional `rng: impl RngCore` argument to `builder::InProgress::create_proof`, `builder::Bundle::create_proof`, `circuit::Proof::create`. +- `halo2_gadgets::ecc`: + - `EccInstructions::ScalarVar` is now treated as a full-width scalar, instead + of being restricted to a base field element. + - `EccInstructions::mul` now takes a `Self::ScalarVar` as argument, instead + of assuming that the scalar fits in a base field element `Self::Var`. + - `EccInstructions::mul_fixed` now takes a `Self::ScalarFixed` as argument, + instead of requiring that the chip always witness a new scalar. + - `EccInstructions::mul_fixed_short` now takes a `Self::ScalarFixedShort` as + argument, instead of the magnitude and sign directly. + - `FixedPoint::mul` now takes `ScalarFixed` instead of `Option`. + - `FixedPointShort::mul` now takes `ScalarFixedShort` instead of + `(EccChip::Var, EccChip::Var)`. +- `halo2_gadgets::ecc::chip`: + - `FixedPoint::u` now returns `Vec<[::Repr; H]>` + instead of `Vec<[[u8; 32]; H]>`. + - `ScalarKind` has been renamed to `FixedScalarKind`. +- `halo2_gadgets::sinsemilla`: + - `CommitDomain::{commit, short_commit}` now take the trapdoor `r` as an + `ecc::ScalarFixed` instead of `Option`. + - `merkle::MerklePath` can now be constructed with more or fewer than two + `MerkleChip`s. + ### Removed -- `orchard::value::ValueSum::from_raw` +- `halo2_gadgets::primitives` (use `halo2_gadgets::poseidon::primitives` or + `halo2_gadgets::sinsemilla::primitives` instead). + +## [0.1.0-beta.3] - 2022-04-06 +### Changed +- Migrated to `halo2_proofs 0.1.0-beta.4`. + +## [0.1.0-beta.2] - 2022-03-22 +### Changed +- Migrated to `halo2_proofs 0.1.0-beta.3`. -## [0.1.0-beta.1] - 2021-12-17 +## [0.1.0-beta.1] - 2022-02-14 Initial release! diff --git a/halo2_gadgets/Cargo.toml b/halo2_gadgets/Cargo.toml index 3174693386..e07d3af198 100644 --- a/halo2_gadgets/Cargo.toml +++ b/halo2_gadgets/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "halo2_gadgets" -version = "0.0.0" +version = "0.2.0" authors = [ "Sean Bowe ", "Jack Grigg ", @@ -8,29 +8,30 @@ authors = [ "Ying Tong Lai ", "Kris Nuttycombe ", ] -edition = "2018" -description = "[BETA] Reusable gadgets and chip implementations for Halo 2" -license-file = "../COPYING" +edition = "2021" +rust-version = "1.56.1" +description = "Reusable gadgets and chip implementations for Halo 2" +license = "MIT OR Apache-2.0" repository = "https://github.com/zcash/halo2" readme = "README.md" categories = ["cryptography"] -keywords = ["zcash"] +keywords = ["halo", "proofs", "zcash", "zkp", "zkSNARKs"] [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "../katex-header.html"] +rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] [dependencies] arrayvec = "0.7.0" -bitvec = "0.22" -ff = "0.11" -group = "0.11" -halo2_proofs = { version = "=0.1.0-beta.1", path = "../halo2_proofs" } +bitvec = "1" +ff = "0.12" +group = "0.12" +halo2_proofs = { version = "0.2", path = "../halo2_proofs" } lazy_static = "1" proptest = { version = "1.0.0", optional = true } rand = "0.8" subtle = "2.3" -uint = "=0.9.1" # uint 0.9.2 bumps the MSRV to 1.56.1 +uint = "0.9.2" # MSRV 1.56.1 # Developer tooling dependencies plotters = { version = "0.3.0", optional = true } @@ -40,7 +41,7 @@ criterion = "0.3" proptest = "1.0.0" [target.'cfg(unix)'.dev-dependencies] -pprof = { version = "=0.6.1", features = ["criterion", "flamegraph"] } +pprof = { version = "0.8", features = ["criterion", "flamegraph"] } # MSRV 1.56 [lib] bench = false diff --git a/halo2_gadgets/README.md b/halo2_gadgets/README.md index db1aa509ae..4ed744f81f 100644 --- a/halo2_gadgets/README.md +++ b/halo2_gadgets/README.md @@ -1,8 +1,6 @@ # halo2_gadgets [![Crates.io](https://img.shields.io/crates/v/halo2_gadgets.svg)](https://crates.io/crates/halo2_gadgets) # -**IMPORTANT**: This library is being actively developed and should not be used in production software. - -Requires Rust 1.51+. +Requires Rust 1.56.1+. ## Documentation @@ -11,14 +9,17 @@ Requires Rust 1.51+. ## License -Copyright 2020-2021 The Electric Coin Company. +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. -You may use this package under the Bootstrap Open Source Licence, version 1.0, -or at your option, any later version. See the file [`COPYING`](COPYING) for -more details, and [`LICENSE-BOSL`](LICENSE-BOSL) for the terms of the Bootstrap -Open Source Licence, version 1.0. +### Contribution -The purpose of the BOSL is to allow commercial improvements to the package -while ensuring that all improvements are open source. See -[here](https://electriccoin.co/blog/introducing-tgppl-a-radically-new-type-of-open-source-license/) -for why the BOSL exists. +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/halo2_gadgets/katex-header.html b/halo2_gadgets/katex-header.html new file mode 100644 index 0000000000..98e85904fa --- /dev/null +++ b/halo2_gadgets/katex-header.html @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/halo2_gadgets/rust-toolchain b/halo2_gadgets/rust-toolchain deleted file mode 100644 index dabc0d9f12..0000000000 --- a/halo2_gadgets/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly-2021-11-17 \ No newline at end of file diff --git a/halo2_gadgets/src/lib.rs b/halo2_gadgets/src/lib.rs index b8bfa592c0..cffa4e0b23 100644 --- a/halo2_gadgets/src/lib.rs +++ b/halo2_gadgets/src/lib.rs @@ -1,4 +1,18 @@ -//! # halo2_gadgets +//! This crate provides various common gadgets and chips for use with `halo2_proofs`. +//! +//! # Gadgets +//! +//! Gadgets are an abstraction for writing reusable and interoperable circuit logic. They +//! do not create any circuit constraints or assignments themselves, instead interacting +//! with the circuit through a defined "instruction set". A circuit developer uses gadgets +//! by instantiating them with a particular choice of chip. +//! +//! # Chips +//! +//! Chips implement the low-level circuit constraints. The same instructions may be +//! implemented by multiple chips, enabling different performance trade-offs to be made. +//! Chips can be highly optimised by their developers, as long as they conform to the +//! defined instructions. #![cfg_attr(docsrs, feature(doc_cfg))] // Temporary until we have more of the crate implemented. diff --git a/halo2_gadgets/src/sha256.rs b/halo2_gadgets/src/sha256.rs index d386fe6699..19a658df3a 100644 --- a/halo2_gadgets/src/sha256.rs +++ b/halo2_gadgets/src/sha256.rs @@ -1,4 +1,4 @@ -//! Gadget and chips for the [SHA-256] hash function. +//! The [SHA-256] hash function. //! //! [SHA-256]: https://tools.ietf.org/html/rfc6234 diff --git a/halo2_gadgets/src/sha256/table16.rs b/halo2_gadgets/src/sha256/table16.rs index e16756e125..6dc4531b0c 100644 --- a/halo2_gadgets/src/sha256/table16.rs +++ b/halo2_gadgets/src/sha256/table16.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use super::Sha256Instructions; use halo2_proofs::{ - circuit::{AssignedCell, Chip, Layouter, Region}, + circuit::{AssignedCell, Chip, Layouter, Region, Value}, pairing::bn256::Fr, plonk::{Advice, Any, Assigned, Column, ConstraintSystem, Error}, }; @@ -49,7 +49,7 @@ const IV: [u32; STATE] = [ #[derive(Clone, Copy, Debug, Default)] /// A word in a `Table16` message block. // TODO: Make the internals of this struct private. -pub struct BlockWord(pub Option); +pub struct BlockWord(pub Value); #[derive(Clone, Debug)] /// Little-endian bits (up to 64 bits) @@ -129,26 +129,26 @@ impl AssignedBits { annotation: A, column: impl Into>, offset: usize, - value: Option, + value: Value, ) -> Result where A: Fn() -> AR, AR: Into, >::Error: std::fmt::Debug, { - let value: Option<[bool; LEN]> = value.map(|v| v.try_into().unwrap()); - let value: Option> = value.map(|v| v.into()); + let value: Value<[bool; LEN]> = value.map(|v| v.try_into().unwrap()); + let value: Value> = value.map(|v| v.into()); let column: Column = column.into(); match column.column_type() { Any::Advice => { region.assign_advice(annotation, column.try_into().unwrap(), offset, || { - value.clone().ok_or(Error::Synthesis) + value.clone() }) } Any::Fixed => { region.assign_fixed(annotation, column.try_into().unwrap(), offset, || { - value.clone().ok_or(Error::Synthesis) + value.clone() }) } _ => panic!("Cannot assign to instance column"), @@ -158,7 +158,7 @@ impl AssignedBits { } impl AssignedBits<16> { - fn value_u16(&self) -> Option { + fn value_u16(&self) -> Value { self.value().map(|v| v.into()) } @@ -167,23 +167,23 @@ impl AssignedBits<16> { annotation: A, column: impl Into>, offset: usize, - value: Option, + value: Value, ) -> Result where A: Fn() -> AR, AR: Into, { let column: Column = column.into(); - let value: Option> = value.map(|v| v.into()); + let value: Value> = value.map(|v| v.into()); match column.column_type() { Any::Advice => { region.assign_advice(annotation, column.try_into().unwrap(), offset, || { - value.clone().ok_or(Error::Synthesis) + value.clone() }) } Any::Fixed => { region.assign_fixed(annotation, column.try_into().unwrap(), offset, || { - value.clone().ok_or(Error::Synthesis) + value.clone() }) } _ => panic!("Cannot assign to instance column"), @@ -193,7 +193,7 @@ impl AssignedBits<16> { } impl AssignedBits<32> { - fn value_u32(&self) -> Option { + fn value_u32(&self) -> Value { self.value().map(|v| v.into()) } @@ -202,23 +202,23 @@ impl AssignedBits<32> { annotation: A, column: impl Into>, offset: usize, - value: Option, + value: Value, ) -> Result where A: Fn() -> AR, AR: Into, { let column: Column = column.into(); - let value: Option> = value.map(|v| v.into()); + let value: Value> = value.map(|v| v.into()); match column.column_type() { Any::Advice => { region.assign_advice(annotation, column.try_into().unwrap(), offset, || { - value.clone().ok_or(Error::Synthesis) + value.clone() }) } Any::Fixed => { region.assign_fixed(annotation, column.try_into().unwrap(), offset, || { - value.clone().ok_or(Error::Synthesis) + value.clone() }) } _ => panic!("Cannot assign to instance column"), @@ -376,10 +376,10 @@ trait Table16Assignment { lookup: &SpreadInputs, a_3: Column, row: usize, - r_0_even: Option<[bool; 16]>, - r_0_odd: Option<[bool; 16]>, - r_1_even: Option<[bool; 16]>, - r_1_odd: Option<[bool; 16]>, + r_0_even: Value<[bool; 16]>, + r_0_odd: Value<[bool; 16]>, + r_1_even: Value<[bool; 16]>, + r_1_odd: Value<[bool; 16]>, ) -> Result< ( (AssignedBits<16>, AssignedBits<16>), @@ -428,10 +428,10 @@ trait Table16Assignment { lookup: &SpreadInputs, a_3: Column, row: usize, - r_0_even: Option<[bool; 16]>, - r_0_odd: Option<[bool; 16]>, - r_1_even: Option<[bool; 16]>, - r_1_odd: Option<[bool; 16]>, + r_0_even: Value<[bool; 16]>, + r_0_odd: Value<[bool; 16]>, + r_1_even: Value<[bool; 16]>, + r_1_odd: Value<[bool; 16]>, ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { let (even, _odd) = self.assign_spread_outputs( region, lookup, a_3, row, r_0_even, r_0_odd, r_1_even, r_1_odd, diff --git a/halo2_gadgets/src/sha256/table16/compression.rs b/halo2_gadgets/src/sha256/table16/compression.rs index b22b2ef4fa..ecff28bab4 100644 --- a/halo2_gadgets/src/sha256/table16/compression.rs +++ b/halo2_gadgets/src/sha256/table16/compression.rs @@ -4,7 +4,7 @@ use super::{ AssignedBits, BlockWord, SpreadInputs, SpreadVar, Table16Assignment, ROUNDS, STATE, }; use halo2_proofs::{ - circuit::Layouter, + circuit::{Layouter, Value}, pairing::bn256::Fr, plonk::{Advice, Column, ConstraintSystem, Error, Selector}, poly::Rotation, @@ -27,12 +27,12 @@ pub trait UpperSigmaVar< const D_LEN: usize, > { - fn spread_a(&self) -> Option<[bool; A_LEN]>; - fn spread_b(&self) -> Option<[bool; B_LEN]>; - fn spread_c(&self) -> Option<[bool; C_LEN]>; - fn spread_d(&self) -> Option<[bool; D_LEN]>; + fn spread_a(&self) -> Value<[bool; A_LEN]>; + fn spread_b(&self) -> Value<[bool; B_LEN]>; + fn spread_c(&self) -> Value<[bool; C_LEN]>; + fn spread_d(&self) -> Value<[bool; D_LEN]>; - fn xor_upper_sigma(&self) -> Option<[bool; 64]> { + fn xor_upper_sigma(&self) -> Value<[bool; 64]> { self.spread_a() .zip(self.spread_b()) .zip(self.spread_c()) @@ -128,15 +128,15 @@ impl AbcdVar { } impl UpperSigmaVar<4, 22, 18, 20> for AbcdVar { - fn spread_a(&self) -> Option<[bool; 4]> { + fn spread_a(&self) -> Value<[bool; 4]> { self.a.spread.value().map(|v| v.0) } - fn spread_b(&self) -> Option<[bool; 22]> { + fn spread_b(&self) -> Value<[bool; 22]> { self.b.spread.value().map(|v| v.0) } - fn spread_c(&self) -> Option<[bool; 18]> { + fn spread_c(&self) -> Value<[bool; 18]> { self.c_lo .spread .value() @@ -153,7 +153,7 @@ impl UpperSigmaVar<4, 22, 18, 20> for AbcdVar { }) } - fn spread_d(&self) -> Option<[bool; 20]> { + fn spread_d(&self) -> Value<[bool; 20]> { self.d.spread.value().map(|v| v.0) } } @@ -217,7 +217,7 @@ impl EfghVar { } impl UpperSigmaVar<12, 10, 28, 14> for EfghVar { - fn spread_a(&self) -> Option<[bool; 12]> { + fn spread_a(&self) -> Value<[bool; 12]> { self.a_lo .spread .value() @@ -232,7 +232,7 @@ impl UpperSigmaVar<12, 10, 28, 14> for EfghVar { }) } - fn spread_b(&self) -> Option<[bool; 10]> { + fn spread_b(&self) -> Value<[bool; 10]> { self.b_lo .spread .value() @@ -247,11 +247,11 @@ impl UpperSigmaVar<12, 10, 28, 14> for EfghVar { }) } - fn spread_c(&self) -> Option<[bool; 28]> { + fn spread_c(&self) -> Value<[bool; 28]> { self.c.spread.value().map(|v| v.0) } - fn spread_d(&self) -> Option<[bool; 14]> { + fn spread_d(&self) -> Value<[bool; 14]> { self.d.spread.value().map(|v| v.0) } } @@ -266,7 +266,7 @@ impl From<(AssignedBits<16>, AssignedBits<16>)> for RoundWordDense { } impl RoundWordDense { - pub fn value(&self) -> Option { + pub fn value(&self) -> Value { self.0 .value_u16() .zip(self.1.value_u16()) @@ -284,7 +284,7 @@ impl From<(AssignedBits<32>, AssignedBits<32>)> for RoundWordSpread { } impl RoundWordSpread { - pub fn value(&self) -> Option { + pub fn value(&self) -> Value { self.0 .value_u32() .zip(self.1.value_u32()) @@ -922,7 +922,7 @@ impl CompressionConfig { layouter: &mut impl Layouter, state: State, ) -> Result<[BlockWord; DIGEST_SIZE], Error> { - let mut digest = [BlockWord(Some(0)); DIGEST_SIZE]; + let mut digest = [BlockWord(Value::known(0)); DIGEST_SIZE]; layouter.assign_region( || "digest", |mut region| { @@ -984,10 +984,10 @@ mod tests { let digest = config.compression.digest(&mut layouter, state)?; for (idx, digest_word) in digest.iter().enumerate() { - assert_eq!( - (digest_word.0.unwrap() as u64 + IV[idx] as u64) as u32, - super::compression_util::COMPRESSION_OUTPUT[idx] - ); + digest_word.0.assert_if_known(|digest_word| { + (*digest_word as u64 + IV[idx] as u64) as u32 + == super::compression_util::COMPRESSION_OUTPUT[idx] + }); } Ok(()) diff --git a/halo2_gadgets/src/sha256/table16/compression/compression_gates.rs b/halo2_gadgets/src/sha256/table16/compression/compression_gates.rs index 4c0a03dde2..e22a10210c 100644 --- a/halo2_gadgets/src/sha256/table16/compression/compression_gates.rs +++ b/halo2_gadgets/src/sha256/table16/compression/compression_gates.rs @@ -1,6 +1,9 @@ use super::super::{util::*, Gate}; -use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; -use std::{array, marker::PhantomData}; +use halo2_proofs::{ + arithmetic::FieldExt, + plonk::{Constraint, Constraints, Expression}, +}; +use std::marker::PhantomData; pub struct CompressionGate(PhantomData); @@ -32,7 +35,11 @@ impl CompressionGate { spread_word_lo: Expression, word_hi: Expression, spread_word_hi: Expression, - ) -> impl Iterator)> { + ) -> Constraints< + F, + (&'static str, Expression), + impl Iterator)>, + > { let check_spread_and_range = Gate::three_bit_spread_and_range(c_lo.clone(), spread_c_lo.clone()) .chain(Gate::three_bit_spread_and_range( @@ -63,12 +70,14 @@ impl CompressionGate { + spread_word_lo * (-F::one()) + spread_word_hi * F::from(1 << 32) * (-F::one()); - check_spread_and_range - .chain(Some(("range_check_tag_b", range_check_tag_b))) - .chain(Some(("range_check_tag_d", range_check_tag_d))) - .chain(Some(("dense_check", dense_check))) - .chain(Some(("spread_check", spread_check))) - .map(move |(name, poly)| (name, s_decompose_abcd.clone() * poly)) + Constraints::with_selector( + s_decompose_abcd, + check_spread_and_range + .chain(Some(("range_check_tag_b", range_check_tag_b))) + .chain(Some(("range_check_tag_d", range_check_tag_d))) + .chain(Some(("dense_check", dense_check))) + .chain(Some(("spread_check", spread_check))), + ) } // Decompose `E,F,G,H` words @@ -94,7 +103,11 @@ impl CompressionGate { spread_word_lo: Expression, word_hi: Expression, spread_word_hi: Expression, - ) -> impl Iterator)> { + ) -> Constraints< + F, + (&'static str, Expression), + impl Iterator)>, + > { let check_spread_and_range = Gate::three_bit_spread_and_range(a_lo.clone(), spread_a_lo.clone()) .chain(Gate::three_bit_spread_and_range( @@ -128,12 +141,14 @@ impl CompressionGate { + spread_word_lo * (-F::one()) + spread_word_hi * F::from(1 << 32) * (-F::one()); - check_spread_and_range - .chain(Some(("range_check_tag_c", range_check_tag_c))) - .chain(Some(("range_check_tag_d", range_check_tag_d))) - .chain(Some(("dense_check", dense_check))) - .chain(Some(("spread_check", spread_check))) - .map(move |(name, poly)| (name, s_decompose_efgh.clone() * poly)) + Constraints::with_selector( + s_decompose_efgh, + check_spread_and_range + .chain(Some(("range_check_tag_c", range_check_tag_c))) + .chain(Some(("range_check_tag_d", range_check_tag_d))) + .chain(Some(("dense_check", dense_check))) + .chain(Some(("spread_check", spread_check))), + ) } // s_upper_sigma_0 on abcd words @@ -151,7 +166,7 @@ impl CompressionGate { spread_c_mid: Expression, spread_c_hi: Expression, spread_d: Expression, - ) -> impl Iterator)> { + ) -> Option<(&'static str, Expression)> { let spread_witness = spread_r0_even + spread_r0_odd * F::from(2) + (spread_r1_even + spread_r1_odd * F::from(2)) * F::from(1 << 32); @@ -176,7 +191,7 @@ impl CompressionGate { let xor = xor_0 + xor_1 + xor_2; let check = spread_witness + (xor * -F::one()); - std::iter::empty().chain(Some(("s_upper_sigma_0", s_upper_sigma_0 * check))) + Some(("s_upper_sigma_0", s_upper_sigma_0 * check)) } // s_upper_sigma_1 on efgh words @@ -194,7 +209,7 @@ impl CompressionGate { spread_b_hi: Expression, spread_c: Expression, spread_d: Expression, - ) -> impl Iterator)> { + ) -> Option<(&'static str, Expression)> { let spread_witness = spread_r0_even + spread_r0_odd * F::from(2) + (spread_r1_even + spread_r1_odd * F::from(2)) * F::from(1 << 32); @@ -220,7 +235,7 @@ impl CompressionGate { let xor = xor_0 + xor_1 + xor_2; let check = spread_witness + (xor * -F::one()); - std::iter::empty().chain(Some(("s_upper_sigma_1", s_upper_sigma_1 * check))) + Some(("s_upper_sigma_1", s_upper_sigma_1 * check)) } // First part of choice gate on (E, F, G), E ∧ F @@ -235,7 +250,7 @@ impl CompressionGate { spread_e_hi: Expression, spread_f_lo: Expression, spread_f_hi: Expression, - ) -> impl Iterator)> { + ) -> Option<(&'static str, Expression)> { let lhs_lo = spread_e_lo + spread_f_lo; let lhs_hi = spread_e_hi + spread_f_hi; let lhs = lhs_lo + lhs_hi * F::from(1 << 32); @@ -246,7 +261,7 @@ impl CompressionGate { let check = lhs + rhs * -F::one(); - std::iter::empty().chain(Some(("s_ch", s_ch * check))) + Some(("s_ch", s_ch * check)) } // Second part of Choice gate on (E, F, G), ¬E ∧ G @@ -263,7 +278,11 @@ impl CompressionGate { spread_e_neg_hi: Expression, spread_g_lo: Expression, spread_g_hi: Expression, - ) -> impl Iterator)> { + ) -> Constraints< + F, + (&'static str, Expression), + impl Iterator)>, + > { let neg_check = { let evens = Self::ones() * F::from(MASK_EVEN_32 as u64); // evens - spread_e_lo = spread_e_neg_lo @@ -284,9 +303,7 @@ impl CompressionGate { let rhs_odd = spread_q0_odd + spread_q1_odd * F::from(1 << 32); let rhs = rhs_even + rhs_odd * F::from(2); - neg_check - .chain(Some(("s_ch_neg", lhs - rhs))) - .map(move |(name, poly)| (name, s_ch_neg.clone() * poly)) + Constraints::with_selector(s_ch_neg, neg_check.chain(Some(("s_ch_neg", lhs - rhs)))) } // Majority gate on (A, B, C) @@ -303,7 +320,7 @@ impl CompressionGate { spread_b_hi: Expression, spread_c_lo: Expression, spread_c_hi: Expression, - ) -> impl Iterator)> { + ) -> Option<(&'static str, Expression)> { let maj_even = spread_m_0_even + spread_m_1_even * F::from(1 << 32); let maj_odd = spread_m_0_odd + spread_m_1_odd * F::from(1 << 32); let maj = maj_even + maj_odd * F::from(2); @@ -313,7 +330,7 @@ impl CompressionGate { let c = spread_c_lo + spread_c_hi * F::from(1 << 32); let sum = a + b + c; - std::iter::empty().chain(Some(("maj", s_maj * (sum - maj)))) + Some(("maj", s_maj * (sum - maj))) } // s_h_prime to get H' = H + Ch(E, F, G) + s_upper_sigma_1(E) + K + W @@ -335,7 +352,7 @@ impl CompressionGate { k_hi: Expression, w_lo: Expression, w_hi: Expression, - ) -> impl Iterator)> { + ) -> Option<(&'static str, Expression)> { let lo = h_lo + ch_lo + ch_neg_lo + sigma_e_lo + k_lo + w_lo; let hi = h_hi + ch_hi + ch_neg_hi + sigma_e_hi + k_hi + w_hi; @@ -344,7 +361,7 @@ impl CompressionGate { let check = sum - (h_prime_carry * F::from(1 << 32)) - h_prime; - std::iter::empty().chain(Some(("s_h_prime", s_h_prime * check))) + Some(("s_h_prime", s_h_prime * check)) } // s_a_new to get A_new = H' + Maj(A, B, C) + s_upper_sigma_0(A) @@ -360,7 +377,7 @@ impl CompressionGate { maj_abc_hi: Expression, h_prime_lo: Expression, h_prime_hi: Expression, - ) -> impl Iterator)> { + ) -> Option<(&'static str, Expression)> { let lo = sigma_a_lo + maj_abc_lo + h_prime_lo; let hi = sigma_a_hi + maj_abc_hi + h_prime_hi; let sum = lo + hi * F::from(1 << 16); @@ -368,7 +385,7 @@ impl CompressionGate { let check = sum - (a_new_carry * F::from(1 << 32)) - a_new; - std::iter::empty().chain(Some(("s_a_new", s_a_new * check))) + Some(("s_a_new", s_a_new * check)) } // s_e_new to get E_new = H' + D @@ -382,7 +399,7 @@ impl CompressionGate { d_hi: Expression, h_prime_lo: Expression, h_prime_hi: Expression, - ) -> impl Iterator)> { + ) -> Option<(&'static str, Expression)> { let lo = h_prime_lo + d_lo; let hi = h_prime_hi + d_hi; let sum = lo + hi * F::from(1 << 16); @@ -390,7 +407,7 @@ impl CompressionGate { let check = sum - (e_new_carry * F::from(1 << 32)) - e_new; - std::iter::empty().chain(Some(("s_e_new", s_e_new * check))) + Some(("s_e_new", s_e_new * check)) } // s_digest on final round @@ -409,17 +426,19 @@ impl CompressionGate { lo_3: Expression, hi_3: Expression, word_3: Expression, - ) -> impl Iterator)> { + ) -> impl IntoIterator> { let check_lo_hi = |lo: Expression, hi: Expression, word: Expression| { lo + hi * F::from(1 << 16) - word }; - array::IntoIter::new([ - ("check_lo_hi_0", check_lo_hi(lo_0, hi_0, word_0)), - ("check_lo_hi_1", check_lo_hi(lo_1, hi_1, word_1)), - ("check_lo_hi_2", check_lo_hi(lo_2, hi_2, word_2)), - ("check_lo_hi_3", check_lo_hi(lo_3, hi_3, word_3)), - ]) - .map(move |(name, poly)| (name, s_digest.clone() * poly)) + Constraints::with_selector( + s_digest, + [ + ("check_lo_hi_0", check_lo_hi(lo_0, hi_0, word_0)), + ("check_lo_hi_1", check_lo_hi(lo_1, hi_1, word_1)), + ("check_lo_hi_2", check_lo_hi(lo_2, hi_2, word_2)), + ("check_lo_hi_3", check_lo_hi(lo_3, hi_3, word_3)), + ], + ) } } diff --git a/halo2_gadgets/src/sha256/table16/compression/compression_util.rs b/halo2_gadgets/src/sha256/table16/compression/compression_util.rs index 418ff259b3..9506fdcaf0 100644 --- a/halo2_gadgets/src/sha256/table16/compression/compression_util.rs +++ b/halo2_gadgets/src/sha256/table16/compression/compression_util.rs @@ -6,7 +6,7 @@ use crate::sha256::table16::{ util::*, AssignedBits, SpreadVar, SpreadWord, StateWord, Table16Assignment, }; use halo2_proofs::{ - circuit::Region, + circuit::{Region, Value}, pairing::bn256::Fr, plonk::{Advice, Column, Error}, }; @@ -39,70 +39,77 @@ pub const SUBREGION_MAIN_WORD: usize = DECOMPOSE_ABCD + SIGMA_0_ROWS + DECOMPOSE_EFGH + SIGMA_1_ROWS + CH_ROWS + MAJ_ROWS; pub const SUBREGION_MAIN_ROWS: usize = SUBREGION_MAIN_LEN * SUBREGION_MAIN_WORD; +/// The initial round. +pub struct InitialRound; + +/// A main round index. +#[derive(Debug, Copy, Clone)] +pub struct MainRoundIdx(usize); + /// Round index. #[derive(Debug, Copy, Clone)] pub enum RoundIdx { Init, - Main(usize), + Main(MainRoundIdx), +} + +impl From for RoundIdx { + fn from(_: InitialRound) -> Self { + RoundIdx::Init + } } -impl RoundIdx { +impl From for RoundIdx { + fn from(idx: MainRoundIdx) -> Self { + RoundIdx::Main(idx) + } +} + +impl MainRoundIdx { pub(crate) fn as_usize(&self) -> usize { - match self { - Self::Main(idx) => *idx, - _ => panic!(), - } + self.0 } } -impl From for RoundIdx { +impl From for MainRoundIdx { fn from(idx: usize) -> Self { - Self::Main(idx) + MainRoundIdx(idx) } } -impl std::ops::Add for RoundIdx { +impl std::ops::Add for MainRoundIdx { type Output = Self; fn add(self, rhs: usize) -> Self::Output { - match self { - Self::Main(idx) => Self::Main(idx + rhs), - _ => panic!(), - } + MainRoundIdx(self.0 + rhs) } } -impl Ord for RoundIdx { +impl Ord for MainRoundIdx { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - match (self, other) { - (Self::Main(idx_0), Self::Main(idx_1)) => idx_0.cmp(idx_1), - _ => panic!(), - } + self.0.cmp(&other.0) } } -impl PartialOrd for RoundIdx { +impl PartialOrd for MainRoundIdx { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl PartialEq for RoundIdx { +impl PartialEq for MainRoundIdx { fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Main(idx_0), Self::Main(idx_1)) => idx_0 == idx_1, - _ => panic!(), - } + self.0 == other.0 } } -impl Eq for RoundIdx {} +impl Eq for MainRoundIdx {} /// Returns starting row number of a compression round pub fn get_round_row(round_idx: RoundIdx) -> usize { match round_idx { RoundIdx::Init => 0, - RoundIdx::Main(idx) => { + RoundIdx::Main(MainRoundIdx(idx)) => { assert!(idx < 64); (idx as usize) * SUBREGION_MAIN_WORD } @@ -113,81 +120,73 @@ pub fn get_decompose_e_row(round_idx: RoundIdx) -> usize { get_round_row(round_idx) } -pub fn get_decompose_f_row(round_idx: RoundIdx) -> usize { - assert!(matches!(round_idx, RoundIdx::Init)); - get_decompose_e_row(round_idx) + DECOMPOSE_EFGH +pub fn get_decompose_f_row(round_idx: InitialRound) -> usize { + get_decompose_e_row(round_idx.into()) + DECOMPOSE_EFGH } -pub fn get_decompose_g_row(round_idx: RoundIdx) -> usize { +pub fn get_decompose_g_row(round_idx: InitialRound) -> usize { get_decompose_f_row(round_idx) + DECOMPOSE_EFGH } -pub fn get_upper_sigma_1_row(round_idx: RoundIdx) -> usize { - assert!(matches!(round_idx, RoundIdx::Main(_))); - get_decompose_e_row(round_idx) + DECOMPOSE_EFGH + 1 +pub fn get_upper_sigma_1_row(round_idx: MainRoundIdx) -> usize { + get_decompose_e_row(round_idx.into()) + DECOMPOSE_EFGH + 1 } -pub fn get_ch_row(round_idx: RoundIdx) -> usize { - assert!(matches!(round_idx, RoundIdx::Main(_))); - get_decompose_e_row(round_idx) + DECOMPOSE_EFGH + SIGMA_1_ROWS + 1 +pub fn get_ch_row(round_idx: MainRoundIdx) -> usize { + get_decompose_e_row(round_idx.into()) + DECOMPOSE_EFGH + SIGMA_1_ROWS + 1 } -pub fn get_ch_neg_row(round_idx: RoundIdx) -> usize { +pub fn get_ch_neg_row(round_idx: MainRoundIdx) -> usize { get_ch_row(round_idx) + CH_ROWS / 2 } pub fn get_decompose_a_row(round_idx: RoundIdx) -> usize { match round_idx { RoundIdx::Init => get_h_row(round_idx) + DECOMPOSE_EFGH, - _ => get_ch_neg_row(round_idx) - 1 + CH_ROWS / 2, + RoundIdx::Main(mri) => get_ch_neg_row(mri) - 1 + CH_ROWS / 2, } } -pub fn get_upper_sigma_0_row(round_idx: RoundIdx) -> usize { - assert!(matches!(round_idx, RoundIdx::Main(_))); - get_decompose_a_row(round_idx) + DECOMPOSE_ABCD + 1 +pub fn get_upper_sigma_0_row(round_idx: MainRoundIdx) -> usize { + get_decompose_a_row(round_idx.into()) + DECOMPOSE_ABCD + 1 } -pub fn get_decompose_b_row(round_idx: RoundIdx) -> usize { - assert!(matches!(round_idx, RoundIdx::Init)); - get_decompose_a_row(round_idx) + DECOMPOSE_ABCD +pub fn get_decompose_b_row(round_idx: InitialRound) -> usize { + get_decompose_a_row(round_idx.into()) + DECOMPOSE_ABCD } -pub fn get_decompose_c_row(round_idx: RoundIdx) -> usize { +pub fn get_decompose_c_row(round_idx: InitialRound) -> usize { get_decompose_b_row(round_idx) + DECOMPOSE_ABCD } -pub fn get_maj_row(round_idx: RoundIdx) -> usize { - assert!(matches!(round_idx, RoundIdx::Main(_))); +pub fn get_maj_row(round_idx: MainRoundIdx) -> usize { get_upper_sigma_0_row(round_idx) + SIGMA_0_ROWS } // Get state word rows pub fn get_h_row(round_idx: RoundIdx) -> usize { match round_idx { - RoundIdx::Init => get_decompose_g_row(round_idx) + DECOMPOSE_EFGH, - _ => get_ch_row(round_idx) - 1, + RoundIdx::Init => get_decompose_g_row(InitialRound) + DECOMPOSE_EFGH, + RoundIdx::Main(mri) => get_ch_row(mri) - 1, } } -pub fn get_h_prime_row(round_idx: RoundIdx) -> usize { - assert!(matches!(round_idx, RoundIdx::Main(_))); +pub fn get_h_prime_row(round_idx: MainRoundIdx) -> usize { get_ch_row(round_idx) } pub fn get_d_row(round_idx: RoundIdx) -> usize { match round_idx { - RoundIdx::Init => get_decompose_c_row(round_idx) + DECOMPOSE_ABCD, - _ => get_ch_row(round_idx) + 2, + RoundIdx::Init => get_decompose_c_row(InitialRound) + DECOMPOSE_ABCD, + RoundIdx::Main(mri) => get_ch_row(mri) + 2, } } -pub fn get_e_new_row(round_idx: RoundIdx) -> usize { - assert!(matches!(round_idx, RoundIdx::Main(_))); - get_d_row(round_idx) +pub fn get_e_new_row(round_idx: MainRoundIdx) -> usize { + get_d_row(round_idx.into()) } -pub fn get_a_new_row(round_idx: RoundIdx) -> usize { +pub fn get_a_new_row(round_idx: MainRoundIdx) -> usize { get_maj_row(round_idx) } @@ -204,7 +203,7 @@ impl CompressionConfig { &self, region: &mut Region<'_, Fr>, row: usize, - val: Option, + val: Value, ) -> Result { self.s_decompose_abcd.enable(region, row)?; @@ -214,7 +213,7 @@ impl CompressionConfig { let a_6 = self.extras[2]; let spread_pieces = val.map(AbcdVar::pieces); - let spread_pieces = transpose_option_vec(spread_pieces, 6); + let spread_pieces = spread_pieces.transpose_vec(6); let a = SpreadVar::without_lookup( region, @@ -275,7 +274,7 @@ impl CompressionConfig { &self, region: &mut Region<'_, Fr>, row: usize, - val: Option, + val: Value, ) -> Result { self.s_decompose_efgh.enable(region, row)?; @@ -285,7 +284,7 @@ impl CompressionConfig { let a_6 = self.extras[2]; let spread_pieces = val.map(EfghVar::pieces); - let spread_pieces = transpose_option_vec(spread_pieces, 6); + let spread_pieces = spread_pieces.transpose_vec(6); let a_lo = SpreadVar::without_lookup( region, @@ -346,7 +345,7 @@ impl CompressionConfig { &self, region: &mut Region<'_, Fr>, round_idx: RoundIdx, - a_val: Option, + a_val: Value, ) -> Result { let row = get_decompose_a_row(round_idx); @@ -359,7 +358,7 @@ impl CompressionConfig { &self, region: &mut Region<'_, Fr>, round_idx: RoundIdx, - e_val: Option, + e_val: Value, ) -> Result { let row = get_decompose_e_row(round_idx); @@ -371,7 +370,7 @@ impl CompressionConfig { pub(super) fn assign_upper_sigma_0( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, + round_idx: MainRoundIdx, word: AbcdVar, ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { // Rename these here for ease of matching the gates to the specification. @@ -406,11 +405,11 @@ impl CompressionConfig { // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} let r = word.xor_upper_sigma(); - let r_0: Option<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); + let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); let r_0_even = r_0.map(even_bits); let r_0_odd = r_0.map(odd_bits); - let r_1: Option<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); + let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); let r_1_even = r_1.map(even_bits); let r_1_odd = r_1.map(odd_bits); @@ -429,7 +428,7 @@ impl CompressionConfig { pub(super) fn assign_upper_sigma_1( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, + round_idx: MainRoundIdx, word: EfghVar, ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { // Rename these here for ease of matching the gates to the specification. @@ -465,11 +464,11 @@ impl CompressionConfig { // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} let r = word.xor_upper_sigma(); - let r_0: Option<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); + let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); let r_0_even = r_0.map(even_bits); let r_0_odd = r_0.map(odd_bits); - let r_1: Option<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); + let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); let r_1_even = r_1.map(even_bits); let r_1_odd = r_1.map(odd_bits); @@ -489,10 +488,10 @@ impl CompressionConfig { &self, region: &mut Region<'_, Fr>, row: usize, - r_0_even: Option<[bool; 16]>, - r_0_odd: Option<[bool; 16]>, - r_1_even: Option<[bool; 16]>, - r_1_odd: Option<[bool; 16]>, + r_0_even: Value<[bool; 16]>, + r_0_odd: Value<[bool; 16]>, + r_1_even: Value<[bool; 16]>, + r_1_odd: Value<[bool; 16]>, ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { let a_3 = self.extras[0]; @@ -513,7 +512,7 @@ impl CompressionConfig { pub(super) fn assign_ch( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, + round_idx: MainRoundIdx, spread_halves_e: RoundWordSpread, spread_halves_f: RoundWordSpread, ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { @@ -540,16 +539,16 @@ impl CompressionConfig { .1 .copy_advice(|| "spread_f_hi", region, a_4, row + 1)?; - let p: Option<[bool; 64]> = spread_halves_e + let p: Value<[bool; 64]> = spread_halves_e .value() .zip(spread_halves_f.value()) .map(|(e, f)| i2lebsp(e + f)); - let p_0: Option<[bool; 32]> = p.map(|p| p[..32].try_into().unwrap()); + let p_0: Value<[bool; 32]> = p.map(|p| p[..32].try_into().unwrap()); let p_0_even = p_0.map(even_bits); let p_0_odd = p_0.map(odd_bits); - let p_1: Option<[bool; 32]> = p.map(|p| p[32..].try_into().unwrap()); + let p_1: Value<[bool; 32]> = p.map(|p| p[32..].try_into().unwrap()); let p_1_even = p_1.map(even_bits); let p_1_odd = p_1.map(odd_bits); @@ -559,7 +558,7 @@ impl CompressionConfig { pub(super) fn assign_ch_neg( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, + round_idx: MainRoundIdx, spread_halves_e: RoundWordSpread, spread_halves_g: RoundWordSpread, ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { @@ -615,7 +614,7 @@ impl CompressionConfig { spread_neg_e_hi, )?; - let p: Option<[bool; 64]> = { + let p: Value<[bool; 64]> = { let spread_neg_e = spread_neg_e_lo .zip(spread_neg_e_hi) .map(|(lo, hi)| lebs2ip(&lo) + (1 << 32) * lebs2ip(&hi)); @@ -624,11 +623,11 @@ impl CompressionConfig { .map(|(neg_e, g)| i2lebsp(neg_e + g)) }; - let p_0: Option<[bool; 32]> = p.map(|p| p[..32].try_into().unwrap()); + let p_0: Value<[bool; 32]> = p.map(|p| p[..32].try_into().unwrap()); let p_0_even = p_0.map(even_bits); let p_0_odd = p_0.map(odd_bits); - let p_1: Option<[bool; 32]> = p.map(|p| p[32..].try_into().unwrap()); + let p_1: Value<[bool; 32]> = p.map(|p| p[32..].try_into().unwrap()); let p_1_even = p_1.map(even_bits); let p_1_odd = p_1.map(odd_bits); @@ -639,10 +638,10 @@ impl CompressionConfig { &self, region: &mut Region<'_, Fr>, row: usize, - r_0_even: Option<[bool; 16]>, - r_0_odd: Option<[bool; 16]>, - r_1_even: Option<[bool; 16]>, - r_1_odd: Option<[bool; 16]>, + r_0_even: Value<[bool; 16]>, + r_0_odd: Value<[bool; 16]>, + r_1_even: Value<[bool; 16]>, + r_1_odd: Value<[bool; 16]>, ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { let a_3 = self.extras[0]; let (_even, odd) = self.assign_spread_outputs( @@ -662,7 +661,7 @@ impl CompressionConfig { pub(super) fn assign_maj( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, + round_idx: MainRoundIdx, spread_halves_a: RoundWordSpread, spread_halves_b: RoundWordSpread, spread_halves_c: RoundWordSpread, @@ -698,17 +697,17 @@ impl CompressionConfig { .1 .copy_advice(|| "spread_c_hi", region, a_5, row + 1)?; - let m: Option<[bool; 64]> = spread_halves_a + let m: Value<[bool; 64]> = spread_halves_a .value() .zip(spread_halves_b.value()) .zip(spread_halves_c.value()) .map(|((a, b), c)| i2lebsp(a + b + c)); - let m_0: Option<[bool; 32]> = m.map(|m| m[..32].try_into().unwrap()); + let m_0: Value<[bool; 32]> = m.map(|m| m[..32].try_into().unwrap()); let m_0_even = m_0.map(even_bits); let m_0_odd = m_0.map(odd_bits); - let m_1: Option<[bool; 32]> = m.map(|m| m[32..].try_into().unwrap()); + let m_1: Value<[bool; 32]> = m.map(|m| m[32..].try_into().unwrap()); let m_1_even = m_1.map(even_bits); let m_1_odd = m_1.map(odd_bits); @@ -720,7 +719,7 @@ impl CompressionConfig { pub(super) fn assign_h_prime( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, + round_idx: MainRoundIdx, h: RoundWordDense, ch: (AssignedBits<16>, AssignedBits<16>), ch_neg: (AssignedBits<16>, AssignedBits<16>), @@ -751,8 +750,8 @@ impl CompressionConfig { let k_lo: [bool; 16] = k[..16].try_into().unwrap(); let k_hi: [bool; 16] = k[16..].try_into().unwrap(); { - AssignedBits::<16>::assign_bits(region, || "k_lo", a_6, row - 1, Some(k_lo))?; - AssignedBits::<16>::assign_bits(region, || "k_hi", a_6, row, Some(k_hi))?; + AssignedBits::<16>::assign_bits(region, || "k_lo", a_6, row - 1, Value::known(k_lo))?; + AssignedBits::<16>::assign_bits(region, || "k_hi", a_6, row, Value::known(k_hi))?; } // Assign and copy w @@ -773,7 +772,10 @@ impl CompressionConfig { (ch.0.value_u16(), ch.1.value_u16()), (ch_neg.0.value_u16(), ch_neg.1.value_u16()), (sigma_1.0.value_u16(), sigma_1.1.value_u16()), - (Some(lebs2ip(&k_lo) as u16), Some(lebs2ip(&k_hi) as u16)), + ( + Value::known(lebs2ip(&k_lo) as u16), + Value::known(lebs2ip(&k_hi) as u16), + ), (w.0.value_u16(), w.1.value_u16()), ]); @@ -788,9 +790,9 @@ impl CompressionConfig { }, )?; - let h_prime: Option<[bool; 32]> = h_prime.map(|w| i2lebsp(w.into())); - let h_prime_lo: Option<[bool; 16]> = h_prime.map(|w| w[..16].try_into().unwrap()); - let h_prime_hi: Option<[bool; 16]> = h_prime.map(|w| w[16..].try_into().unwrap()); + let h_prime: Value<[bool; 32]> = h_prime.map(|w| i2lebsp(w.into())); + let h_prime_lo: Value<[bool; 16]> = h_prime.map(|w| w[..16].try_into().unwrap()); + let h_prime_hi: Value<[bool; 16]> = h_prime.map(|w| w[16..].try_into().unwrap()); let h_prime_lo = AssignedBits::<16>::assign_bits(region, || "h_prime_lo", a_7, row + 1, h_prime_lo)?; @@ -805,7 +807,7 @@ impl CompressionConfig { pub(super) fn assign_e_new( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, + round_idx: MainRoundIdx, d: &RoundWordDense, h_prime: &RoundWordDense, ) -> Result { @@ -842,7 +844,7 @@ impl CompressionConfig { pub(super) fn assign_a_new( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, + round_idx: MainRoundIdx, maj: (AssignedBits<16>, AssignedBits<16>), sigma_0: (AssignedBits<16>, AssignedBits<16>), h_prime: RoundWordDense, @@ -899,17 +901,17 @@ impl CompressionConfig { lo_col: Column, hi_row: usize, hi_col: Column, - word: Option, + word: Value, ) -> Result { - let word: Option<[bool; 32]> = word.map(|w| i2lebsp(w.into())); + let word: Value<[bool; 32]> = word.map(|w| i2lebsp(w.into())); let lo = { - let lo: Option<[bool; 16]> = word.map(|w| w[..16].try_into().unwrap()); + let lo: Value<[bool; 16]> = word.map(|w| w[..16].try_into().unwrap()); AssignedBits::<16>::assign_bits(region, || "lo", lo_col, lo_row, lo)? }; let hi = { - let hi: Option<[bool; 16]> = word.map(|w| w[16..].try_into().unwrap()); + let hi: Value<[bool; 16]> = word.map(|w| w[16..].try_into().unwrap()); AssignedBits::<16>::assign_bits(region, || "hi", hi_col, hi_row, hi)? }; @@ -922,15 +924,15 @@ impl CompressionConfig { &self, region: &mut Region<'_, Fr>, row: usize, - word: Option, + word: Value, ) -> Result<(RoundWordDense, RoundWordSpread), Error> { // Rename these here for ease of matching the gates to the specification. let a_7 = self.extras[3]; let a_8 = self.extras[4]; - let word: Option<[bool; 32]> = word.map(|w| i2lebsp(w.into())); - let lo: Option<[bool; 16]> = word.map(|w| w[..16].try_into().unwrap()); - let hi: Option<[bool; 16]> = word.map(|w| w[16..].try_into().unwrap()); + let word: Value<[bool; 32]> = word.map(|w| i2lebsp(w.into())); + let lo: Value<[bool; 16]> = word.map(|w| w[..16].try_into().unwrap()); + let hi: Value<[bool; 16]> = word.map(|w| w[16..].try_into().unwrap()); let w_lo = SpreadVar::without_lookup(region, a_7, row, a_8, row, lo.map(SpreadWord::new))?; let w_hi = diff --git a/halo2_gadgets/src/sha256/table16/compression/subregion_digest.rs b/halo2_gadgets/src/sha256/table16/compression/subregion_digest.rs index 69b3a8654b..988194a509 100644 --- a/halo2_gadgets/src/sha256/table16/compression/subregion_digest.rs +++ b/halo2_gadgets/src/sha256/table16/compression/subregion_digest.rs @@ -1,7 +1,7 @@ use super::super::{super::DIGEST_SIZE, BlockWord, RoundWordDense}; use super::{compression_util::*, CompressionConfig, State}; use halo2_proofs::{ - circuit::Region, +circuit::{Region, Value}, pairing::bn256::Fr, plonk::{Advice, Column, Error}, }; @@ -85,7 +85,7 @@ impl CompressionConfig { hi_col: Column, word_col: Column, dense_halves: RoundWordDense, - ) -> Result, Error> { + ) -> Result, Error> { dense_halves.0.copy_advice(|| "lo", region, lo_col, row)?; dense_halves.1.copy_advice(|| "hi", region, hi_col, row)?; diff --git a/halo2_gadgets/src/sha256/table16/compression/subregion_initial.rs b/halo2_gadgets/src/sha256/table16/compression/subregion_initial.rs index 0df23b8609..19331002c3 100644 --- a/halo2_gadgets/src/sha256/table16/compression/subregion_initial.rs +++ b/halo2_gadgets/src/sha256/table16/compression/subregion_initial.rs @@ -1,6 +1,6 @@ use super::super::{RoundWord, StateWord, STATE}; use super::{compression_util::*, CompressionConfig, State}; -use halo2_proofs::{circuit::Region, pairing::bn256::Fr, plonk::Error}; +use halo2_proofs::{circuit::{Region, Value}, pairing::bn256::Fr, plonk::Error}; impl CompressionConfig { #[allow(clippy::many_single_char_names)] @@ -12,26 +12,28 @@ impl CompressionConfig { let a_7 = self.extras[3]; // Decompose E into (6, 5, 14, 7)-bit chunks - let e = self.decompose_e(region, RoundIdx::Init, Some(iv[4]))?; + let e = self.decompose_e(region, RoundIdx::Init, Value::known(iv[4]))?; // Decompose F, G - let f = self.decompose_f(region, RoundIdx::Init, Some(iv[5]))?; - let g = self.decompose_g(region, RoundIdx::Init, Some(iv[6]))?; + let f = self.decompose_f(region, InitialRound, Value::known(iv[5]))?; + let g = self.decompose_g(region, InitialRound, Value::known(iv[6]))?; // Assign H let h_row = get_h_row(RoundIdx::Init); - let h = self.assign_word_halves_dense(region, h_row, a_7, h_row + 1, a_7, Some(iv[7]))?; + let h = + self.assign_word_halves_dense(region, h_row, a_7, h_row + 1, a_7, Value::known(iv[7]))?; // Decompose A into (2, 11, 9, 10)-bit chunks - let a = self.decompose_a(region, RoundIdx::Init, Some(iv[0]))?; + let a = self.decompose_a(region, RoundIdx::Init, Value::known(iv[0]))?; // Decompose B, C - let b = self.decompose_b(region, RoundIdx::Init, Some(iv[1]))?; - let c = self.decompose_c(region, RoundIdx::Init, Some(iv[2]))?; + let b = self.decompose_b(region, InitialRound, Value::known(iv[1]))?; + let c = self.decompose_c(region, InitialRound, Value::known(iv[2]))?; // Assign D let d_row = get_d_row(RoundIdx::Init); - let d = self.assign_word_halves_dense(region, d_row, a_7, d_row + 1, a_7, Some(iv[3]))?; + let d = + self.assign_word_halves_dense(region, d_row, a_7, d_row + 1, a_7, Value::known(iv[3]))?; Ok(State::new( StateWord::A(a), @@ -60,9 +62,9 @@ impl CompressionConfig { // Decompose F, G let f = f.dense_halves.value(); - let f = self.decompose_f(region, RoundIdx::Init, f)?; + let f = self.decompose_f(region, InitialRound, f)?; let g = g.dense_halves.value(); - let g = self.decompose_g(region, RoundIdx::Init, g)?; + let g = self.decompose_g(region, InitialRound, g)?; // Assign H let h = h.value(); @@ -75,9 +77,9 @@ impl CompressionConfig { // Decompose B, C let b = b.dense_halves.value(); - let b = self.decompose_b(region, RoundIdx::Init, b)?; + let b = self.decompose_b(region, InitialRound, b)?; let c = c.dense_halves.value(); - let c = self.decompose_c(region, RoundIdx::Init, c)?; + let c = self.decompose_c(region, InitialRound, c)?; // Assign D let d = d.value(); @@ -99,8 +101,8 @@ impl CompressionConfig { fn decompose_b( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, - b_val: Option, + round_idx: InitialRound, + b_val: Value, ) -> Result { let row = get_decompose_b_row(round_idx); @@ -112,8 +114,8 @@ impl CompressionConfig { fn decompose_c( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, - c_val: Option, + round_idx: InitialRound, + c_val: Value, ) -> Result { let row = get_decompose_c_row(round_idx); @@ -125,8 +127,8 @@ impl CompressionConfig { fn decompose_f( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, - f_val: Option, + round_idx: InitialRound, + f_val: Value, ) -> Result { let row = get_decompose_f_row(round_idx); @@ -138,8 +140,8 @@ impl CompressionConfig { fn decompose_g( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, - g_val: Option, + round_idx: InitialRound, + g_val: Value, ) -> Result { let row = get_decompose_g_row(round_idx); diff --git a/halo2_gadgets/src/sha256/table16/compression/subregion_main.rs b/halo2_gadgets/src/sha256/table16/compression/subregion_main.rs index 8faf1e2194..719400c59a 100644 --- a/halo2_gadgets/src/sha256/table16/compression/subregion_main.rs +++ b/halo2_gadgets/src/sha256/table16/compression/subregion_main.rs @@ -7,12 +7,10 @@ impl CompressionConfig { pub fn assign_round( &self, region: &mut Region<'_, Fr>, - round_idx: RoundIdx, + round_idx: MainRoundIdx, state: State, schedule_word: &(AssignedBits<16>, AssignedBits<16>), ) -> Result { - assert!(matches!(round_idx, RoundIdx::Main(_))); - let a_3 = self.extras[0]; let a_4 = self.extras[1]; let a_7 = self.extras[3]; @@ -70,7 +68,7 @@ impl CompressionConfig { if round_idx < 63.into() { // Assign and copy A_new - let a_new_row = get_decompose_a_row(round_idx + 1); + let a_new_row = get_decompose_a_row((round_idx + 1).into()); a_new_dense .0 .copy_advice(|| "a_new_lo", region, a_7, a_new_row)?; @@ -79,7 +77,7 @@ impl CompressionConfig { .copy_advice(|| "a_new_hi", region, a_7, a_new_row + 1)?; // Assign and copy E_new - let e_new_row = get_decompose_e_row(round_idx + 1); + let e_new_row = get_decompose_e_row((round_idx + 1).into()); e_new_dense .0 .copy_advice(|| "e_new_lo", region, a_7, e_new_row)?; @@ -88,10 +86,10 @@ impl CompressionConfig { .copy_advice(|| "e_new_hi", region, a_7, e_new_row + 1)?; // Decompose A into (2, 11, 9, 10)-bit chunks - let a_new = self.decompose_a(region, round_idx + 1, a_new_val)?; + let a_new = self.decompose_a(region, (round_idx + 1).into(), a_new_val)?; // Decompose E into (6, 5, 14, 7)-bit chunks - let e_new = self.decompose_e(region, round_idx + 1, e_new_val)?; + let e_new = self.decompose_e(region, (round_idx + 1).into(), e_new_val)?; Ok(State::new( StateWord::A(a_new), diff --git a/halo2_gadgets/src/sha256/table16/message_schedule.rs b/halo2_gadgets/src/sha256/table16/message_schedule.rs index cf7c2b1812..52d68a1cc2 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule.rs @@ -435,8 +435,10 @@ mod tests { // Run message_scheduler to get W_[0..64] let (w, _) = config.message_schedule.process(&mut layouter, inputs)?; for (word, test_word) in w.iter().zip(MSG_SCHEDULE_TEST_OUTPUT.iter()) { - let word: u32 = lebs2ip(&word.value().unwrap()) as u32; - assert_eq!(word, *test_word); + word.value().assert_if_known(|bits| { + let word: u32 = lebs2ip(bits) as u32; + word == *test_word + }); } Ok(()) } diff --git a/halo2_gadgets/src/sha256/table16/message_schedule/schedule_gates.rs b/halo2_gadgets/src/sha256/table16/message_schedule/schedule_gates.rs index 52d58bd0b1..fab51bd373 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule/schedule_gates.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule/schedule_gates.rs @@ -1,6 +1,6 @@ use super::super::Gate; use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; -use std::{array, marker::PhantomData}; +use std::marker::PhantomData; pub struct ScheduleGate(PhantomData); @@ -29,7 +29,8 @@ impl ScheduleGate { + (word * (-F::one())); let carry_check = Gate::range_check(carry, 0, 3); - array::IntoIter::new([("word_check", word_check), ("carry_check", carry_check)]) + [("word_check", word_check), ("carry_check", carry_check)] + .into_iter() .map(move |(name, poly)| (name, s_word.clone() * poly)) } @@ -39,9 +40,9 @@ impl ScheduleGate { lo: Expression, hi: Expression, word: Expression, - ) -> impl Iterator)> { + ) -> Option<(&'static str, Expression)> { let check = lo + hi * F::from(1 << 16) - word; - std::iter::empty().chain(Some(("s_decompose_0", s_decompose_0 * check))) + Some(("s_decompose_0", s_decompose_0 * check)) } /// s_decompose_1 for W_1 to W_13 @@ -65,11 +66,12 @@ impl ScheduleGate { let range_check_tag_c = Gate::range_check(tag_c, 0, 2); let range_check_tag_d = Gate::range_check(tag_d, 0, 4); - array::IntoIter::new([ + [ ("decompose_check", decompose_check), ("range_check_tag_c", range_check_tag_c), ("range_check_tag_d", range_check_tag_d), - ]) + ] + .into_iter() .map(move |(name, poly)| (name, s_decompose_1.clone() * poly)) } @@ -101,11 +103,12 @@ impl ScheduleGate { let range_check_tag_d = Gate::range_check(tag_d, 0, 0); let range_check_tag_g = Gate::range_check(tag_g, 0, 3); - array::IntoIter::new([ + [ ("decompose_check", decompose_check), ("range_check_tag_g", range_check_tag_g), ("range_check_tag_d", range_check_tag_d), - ]) + ] + .into_iter() .map(move |(name, poly)| (name, s_decompose_2.clone() * poly)) } @@ -130,11 +133,12 @@ impl ScheduleGate { let range_check_tag_a = Gate::range_check(tag_a, 0, 1); let range_check_tag_d = Gate::range_check(tag_d, 0, 3); - array::IntoIter::new([ + [ ("decompose_check", decompose_check), ("range_check_tag_a", range_check_tag_a), ("range_check_tag_d", range_check_tag_d), - ]) + ] + .into_iter() .map(move |(name, poly)| (name, s_decompose_3.clone() * poly)) } diff --git a/halo2_gadgets/src/sha256/table16/message_schedule/schedule_util.rs b/halo2_gadgets/src/sha256/table16/message_schedule/schedule_util.rs index 7fd25bc1bd..dc20bbdc4d 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule/schedule_util.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule/schedule_util.rs @@ -1,6 +1,6 @@ use super::super::AssignedBits; use super::MessageScheduleConfig; -use halo2_proofs::{circuit::Region, pairing::bn256::Fr, plonk::Error}; +use halo2_proofs::{circuit::{Region, Value}, pairing::bn256::Fr, plonk::Error}; #[cfg(test)] use super::super::{super::BLOCK_SIZE, BlockWord, ROUNDS}; @@ -57,22 +57,22 @@ pub fn get_word_row(word_idx: usize) -> usize { #[cfg(test)] pub fn msg_schedule_test_input() -> [BlockWord; BLOCK_SIZE] { [ - BlockWord(Some(0b01100001011000100110001110000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000000000)), - BlockWord(Some(0b00000000000000000000000000011000)), + BlockWord(Value::known(0b01100001011000100110001110000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000000000)), + BlockWord(Value::known(0b00000000000000000000000000011000)), ] } @@ -149,7 +149,7 @@ impl MessageScheduleConfig { pub fn assign_word_and_halves( &self, region: &mut Region<'_, Fr>, - word: Option, + word: Value, word_idx: usize, ) -> Result<(AssignedBits<32>, (AssignedBits<16>, AssignedBits<16>)), Error> { // Rename these here for ease of matching the gates to the specification. diff --git a/halo2_gadgets/src/sha256/table16/message_schedule/subregion1.rs b/halo2_gadgets/src/sha256/table16/message_schedule/subregion1.rs index 01a165e3cb..1a58cf917d 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule/subregion1.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule/subregion1.rs @@ -1,6 +1,6 @@ use super::super::{util::*, AssignedBits, BlockWord, SpreadVar, SpreadWord, Table16Assignment}; use super::{schedule_util::*, MessageScheduleConfig}; -use halo2_proofs::{circuit::Region, pairing::bn256::Fr, plonk::Error}; +use halo2_proofs::{circuit::{Region, Value}, pairing::bn256::Fr, plonk::Error}; use std::convert::TryInto; // A word in subregion 1 @@ -17,23 +17,23 @@ pub struct Subregion1Word { } impl Subregion1Word { - fn spread_a(&self) -> Option<[bool; 6]> { + fn spread_a(&self) -> Value<[bool; 6]> { self.a.value().map(|v| v.spread()) } - fn spread_b(&self) -> Option<[bool; 8]> { + fn spread_b(&self) -> Value<[bool; 8]> { self.b.value().map(|v| v.spread()) } - fn spread_c(&self) -> Option<[bool; 22]> { + fn spread_c(&self) -> Value<[bool; 22]> { self.spread_c.value().map(|v| v.0) } - fn spread_d(&self) -> Option<[bool; 28]> { + fn spread_d(&self) -> Value<[bool; 28]> { self.spread_d.value().map(|v| v.0) } - fn xor_lower_sigma_0(&self) -> Option<[bool; 64]> { + fn xor_lower_sigma_0(&self) -> Value<[bool; 64]> { self.spread_a() .zip(self.spread_b()) .zip(self.spread_c()) @@ -100,7 +100,7 @@ impl MessageScheduleConfig { fn decompose_subregion1_word( &self, region: &mut Region<'_, Fr>, - word: Option<[bool; 32]>, + word: Value<[bool; 32]>, index: usize, ) -> Result { let row = get_word_row(index); @@ -117,7 +117,7 @@ impl MessageScheduleConfig { word[18..32].to_vec(), ] }); - let pieces = transpose_option_vec(pieces, 4); + let pieces = pieces.transpose_vec(4); // Assign `a` (3-bit piece) let a = @@ -168,7 +168,7 @@ impl MessageScheduleConfig { // Split `b` (4-bit chunk) into `b_hi` and `b_lo` // Assign `b_lo`, `spread_b_lo` - let b_lo: Option<[bool; 2]> = word.b.value().map(|b| b.0[..2].try_into().unwrap()); + let b_lo: Value<[bool; 2]> = word.b.value().map(|b| b.0[..2].try_into().unwrap()); let spread_b_lo = b_lo.map(spread_bits); { AssignedBits::<2>::assign_bits(region, || "b_lo", a_3, row - 1, b_lo)?; @@ -178,7 +178,7 @@ impl MessageScheduleConfig { // Split `b` (2-bit chunk) into `b_hi` and `b_lo` // Assign `b_hi`, `spread_b_hi` - let b_hi: Option<[bool; 2]> = word.b.value().map(|b| b.0[2..].try_into().unwrap()); + let b_hi: Value<[bool; 2]> = word.b.value().map(|b| b.0[2..].try_into().unwrap()); let spread_b_hi = b_hi.map(spread_bits); { AssignedBits::<2>::assign_bits(region, || "b_hi", a_5, row - 1, b_hi)?; @@ -197,11 +197,11 @@ impl MessageScheduleConfig { // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} let r = word.xor_lower_sigma_0(); - let r_0: Option<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); + let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); let r_0_even = r_0.map(even_bits); let r_0_odd = r_0.map(odd_bits); - let r_1: Option<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); + let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); let r_1_even = r_1.map(even_bits); let r_1_odd = r_1.map(odd_bits); diff --git a/halo2_gadgets/src/sha256/table16/message_schedule/subregion2.rs b/halo2_gadgets/src/sha256/table16/message_schedule/subregion2.rs index 77622b56f0..62cd428599 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule/subregion2.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule/subregion2.rs @@ -1,6 +1,6 @@ use super::super::{util::*, AssignedBits, Bits, SpreadVar, SpreadWord, Table16Assignment}; use super::{schedule_util::*, MessageScheduleConfig, MessageWord}; -use halo2_proofs::{circuit::Region, pairing::bn256::Fr, plonk::Error}; +use halo2_proofs::{circuit::{Region, Value}, pairing::bn256::Fr, plonk::Error}; use std::convert::TryInto; /// A word in subregion 2 @@ -20,35 +20,35 @@ pub struct Subregion2Word { } impl Subregion2Word { - fn spread_a(&self) -> Option<[bool; 6]> { + fn spread_a(&self) -> Value<[bool; 6]> { self.a.value().map(|v| v.spread()) } - fn spread_b(&self) -> Option<[bool; 8]> { + fn spread_b(&self) -> Value<[bool; 8]> { self.b.value().map(|v| v.spread()) } - fn spread_c(&self) -> Option<[bool; 6]> { + fn spread_c(&self) -> Value<[bool; 6]> { self.c.value().map(|v| v.spread()) } - fn spread_d(&self) -> Option<[bool; 14]> { + fn spread_d(&self) -> Value<[bool; 14]> { self.spread_d.value().map(|v| v.0) } - fn spread_e(&self) -> Option<[bool; 2]> { + fn spread_e(&self) -> Value<[bool; 2]> { self.e.value().map(|v| v.spread()) } - fn spread_f(&self) -> Option<[bool; 2]> { + fn spread_f(&self) -> Value<[bool; 2]> { self.f.value().map(|v| v.spread()) } - fn spread_g(&self) -> Option<[bool; 26]> { + fn spread_g(&self) -> Value<[bool; 26]> { self.spread_g.value().map(|v| v.0) } - fn xor_sigma_0(&self) -> Option<[bool; 64]> { + fn xor_sigma_0(&self) -> Value<[bool; 64]> { self.spread_a() .zip(self.spread_b()) .zip(self.spread_c()) @@ -98,7 +98,7 @@ impl Subregion2Word { }) } - fn xor_sigma_1(&self) -> Option<[bool; 64]> { + fn xor_sigma_1(&self) -> Value<[bool; 64]> { self.spread_a() .zip(self.spread_b()) .zip(self.spread_c()) @@ -260,11 +260,7 @@ impl MessageScheduleConfig { || format!("carry_{}", new_word_idx), a_9, get_word_row(new_word_idx - 16) + 1, - || { - carry - .map(|carry| Fr::from(carry as u64)) - .ok_or(Error::Synthesis) - }, + || carry.map(|carry| Fr::from(carry as u64)), )?; let (word, halves) = self.assign_word_and_halves(region, word, new_word_idx)?; w.push(MessageWord(word)); @@ -294,7 +290,7 @@ impl MessageScheduleConfig { fn decompose_word( &self, region: &mut Region<'_, Fr>, - word: Option<&Bits<32>>, + word: Value<&Bits<32>>, index: usize, ) -> Result { let row = get_word_row(index); @@ -310,7 +306,7 @@ impl MessageScheduleConfig { word[19..32].to_vec(), ] }); - let pieces = transpose_option_vec(pieces, 7); + let pieces = pieces.transpose_vec(7); // Rename these here for ease of matching the gates to the specification. let a_3 = self.extras[0]; @@ -320,14 +316,14 @@ impl MessageScheduleConfig { let a = AssignedBits::<3>::assign_bits(region, || "a", a_3, row - 1, pieces[0].clone())?; // Assign `b` (4-bit piece) lookup - let spread_b: Option> = pieces[1].clone().map(SpreadWord::try_new); + let spread_b: Value> = pieces[1].clone().map(SpreadWord::try_new); let spread_b = SpreadVar::with_lookup(region, &self.lookup, row + 1, spread_b)?; // Assign `c` (3-bit piece) let c = AssignedBits::<3>::assign_bits(region, || "c", a_4, row - 1, pieces[2].clone())?; // Assign `d` (7-bit piece) lookup - let spread_d: Option> = pieces[3].clone().map(SpreadWord::try_new); + let spread_d: Value> = pieces[3].clone().map(SpreadWord::try_new); let spread_d = SpreadVar::with_lookup(region, &self.lookup, row, spread_d)?; // Assign `e` (1-bit piece) @@ -378,7 +374,7 @@ impl MessageScheduleConfig { // Split `b` (4-bit chunk) into `b_hi` and `b_lo` // Assign `b_lo`, `spread_b_lo` - let b_lo: Option<[bool; 2]> = word.b.value().map(|b| b.0[..2].try_into().unwrap()); + let b_lo: Value<[bool; 2]> = word.b.value().map(|b| b.0[..2].try_into().unwrap()); let spread_b_lo = b_lo.map(spread_bits); { AssignedBits::<2>::assign_bits(region, || "b_lo", a_3, row - 1, b_lo)?; @@ -388,7 +384,7 @@ impl MessageScheduleConfig { // Split `b` (2-bit chunk) into `b_hi` and `b_lo` // Assign `b_hi`, `spread_b_hi` - let b_hi: Option<[bool; 2]> = word.b.value().map(|b| b.0[2..].try_into().unwrap()); + let b_hi: Value<[bool; 2]> = word.b.value().map(|b| b.0[2..].try_into().unwrap()); let spread_b_hi = b_hi.map(spread_bits); { AssignedBits::<2>::assign_bits(region, || "b_hi", a_5, row - 1, b_hi)?; @@ -433,11 +429,11 @@ impl MessageScheduleConfig { // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} let r = word.xor_sigma_0(); - let r_0: Option<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); + let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); let r_0_even = r_0.map(even_bits); let r_0_odd = r_0.map(odd_bits); - let r_1: Option<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); + let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); let r_1_even = r_1.map(even_bits); let r_1_odd = r_1.map(odd_bits); @@ -468,11 +464,11 @@ impl MessageScheduleConfig { // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} let r = word.xor_sigma_1(); - let r_0: Option<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); + let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); let r_0_even = r_0.map(even_bits); let r_0_odd = r_0.map(odd_bits); - let r_1: Option<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); + let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); let r_1_even = r_1.map(even_bits); let r_1_odd = r_1.map(odd_bits); diff --git a/halo2_gadgets/src/sha256/table16/message_schedule/subregion3.rs b/halo2_gadgets/src/sha256/table16/message_schedule/subregion3.rs index 3f0985e981..8894bca2d2 100644 --- a/halo2_gadgets/src/sha256/table16/message_schedule/subregion3.rs +++ b/halo2_gadgets/src/sha256/table16/message_schedule/subregion3.rs @@ -1,6 +1,6 @@ use super::super::{util::*, AssignedBits, Bits, SpreadVar, SpreadWord, Table16Assignment}; use super::{schedule_util::*, MessageScheduleConfig, MessageWord}; -use halo2_proofs::{circuit::Region, pairing::bn256::Fr, plonk::Error}; +use halo2_proofs::{circuit::{Region, Value}, pairing::bn256::Fr, plonk::Error}; use std::convert::TryInto; // A word in subregion 3 @@ -18,23 +18,23 @@ pub struct Subregion3Word { } impl Subregion3Word { - fn spread_a(&self) -> Option<[bool; 20]> { + fn spread_a(&self) -> Value<[bool; 20]> { self.spread_a.value().map(|v| v.0) } - fn spread_b(&self) -> Option<[bool; 14]> { + fn spread_b(&self) -> Value<[bool; 14]> { self.b.value().map(|v| v.spread()) } - fn spread_c(&self) -> Option<[bool; 4]> { + fn spread_c(&self) -> Value<[bool; 4]> { self.c.value().map(|v| v.spread()) } - fn spread_d(&self) -> Option<[bool; 26]> { + fn spread_d(&self) -> Value<[bool; 26]> { self.spread_d.value().map(|v| v.0) } - fn xor_lower_sigma_1(&self) -> Option<[bool; 64]> { + fn xor_lower_sigma_1(&self) -> Value<[bool; 64]> { self.spread_a() .zip(self.spread_b()) .zip(self.spread_c()) @@ -167,20 +167,13 @@ impl MessageScheduleConfig { || format!("W_{}", new_word_idx), a_5, get_word_row(new_word_idx - 16) + 1, - || { - word.map(|word| Fr::from(word as u64)) - .ok_or(Error::Synthesis) - }, + || word.map(|word| Fr::from(word as u64)), )?; region.assign_advice( || format!("carry_{}", new_word_idx), a_9, get_word_row(new_word_idx - 16) + 1, - || { - carry - .map(|carry| Fr::from(carry as u64)) - .ok_or(Error::Synthesis) - }, + || carry.map(|carry| Fr::from(carry as u64)), )?; let (word, halves) = self.assign_word_and_halves(region, word, new_word_idx)?; w.push(MessageWord(word)); @@ -200,7 +193,7 @@ impl MessageScheduleConfig { fn decompose_subregion3_word( &self, region: &mut Region<'_, Fr>, - word: Option<&Bits<32>>, + word: Value<&Bits<32>>, index: usize, ) -> Result { let row = get_word_row(index); @@ -217,7 +210,7 @@ impl MessageScheduleConfig { word[19..32].to_vec(), ] }); - let pieces = transpose_option_vec(pieces, 4); + let pieces = pieces.transpose_vec(4); // Assign `a` (10-bit piece) let spread_a = pieces[0].clone().map(SpreadWord::try_new); @@ -264,21 +257,21 @@ impl MessageScheduleConfig { // b_lo (2-bit chunk) { - let b_lo: Option<[bool; 2]> = word.b.value().map(|v| v[0..2].try_into().unwrap()); + let b_lo: Value<[bool; 2]> = word.b.value().map(|v| v[0..2].try_into().unwrap()); let b_lo = b_lo.map(SpreadWord::<2, 4>::new); SpreadVar::without_lookup(region, a_3, row - 1, a_4, row - 1, b_lo)?; } // b_mid (2-bit chunk) { - let b_mid: Option<[bool; 2]> = word.b.value().map(|v| v[2..4].try_into().unwrap()); + let b_mid: Value<[bool; 2]> = word.b.value().map(|v| v[2..4].try_into().unwrap()); let b_mid = b_mid.map(SpreadWord::<2, 4>::new); SpreadVar::without_lookup(region, a_5, row - 1, a_6, row - 1, b_mid)?; } // b_hi (3-bit chunk) { - let b_hi: Option<[bool; 3]> = word.b.value().map(|v| v[4..7].try_into().unwrap()); + let b_hi: Value<[bool; 3]> = word.b.value().map(|v| v[4..7].try_into().unwrap()); let b_hi = b_hi.map(SpreadWord::<3, 6>::new); SpreadVar::without_lookup(region, a_5, row + 1, a_6, row + 1, b_hi)?; } @@ -301,11 +294,11 @@ impl MessageScheduleConfig { // (10, 7, 2, 13) // Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} let r = word.xor_lower_sigma_1(); - let r_0: Option<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); + let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap()); let r_0_even = r_0.map(even_bits); let r_0_odd = r_0.map(odd_bits); - let r_1: Option<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); + let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap()); let r_1_even = r_1.map(even_bits); let r_1_odd = r_1.map(odd_bits); diff --git a/halo2_gadgets/src/sha256/table16/spread_table.rs b/halo2_gadgets/src/sha256/table16/spread_table.rs index 870e9b01be..29413a7ef7 100644 --- a/halo2_gadgets/src/sha256/table16/spread_table.rs +++ b/halo2_gadgets/src/sha256/table16/spread_table.rs @@ -1,7 +1,7 @@ use super::{util::*, AssignedBits}; use halo2_proofs::{ arithmetic::FieldExt, - circuit::{Chip, Layouter, Region}, + circuit::{Chip, Layouter, Region, Value}, pairing::bn256::Fr, plonk::{Advice, Column, ConstraintSystem, Error, TableColumn}, poly::Rotation, @@ -68,7 +68,7 @@ impl SpreadWord { /// A variable stored in advice columns corresponding to a row of [`SpreadTableConfig`]. #[derive(Clone, Debug)] pub(super) struct SpreadVar { - pub tag: Option, + pub tag: Value, pub dense: AssignedBits, pub spread: AssignedBits, } @@ -78,7 +78,7 @@ impl SpreadVar { region: &mut Region<'_, Fr>, cols: &SpreadInputs, row: usize, - word: Option>, + word: Value>, ) -> Result { let tag = word.map(|word| word.tag); let dense_val = word.map(|word| word.dense); @@ -88,7 +88,7 @@ impl SpreadVar { || "tag", cols.tag, row, - || tag.map(|tag| Fr::from(tag as u64)).ok_or(Error::Synthesis), + || tag.map(|tag| Fr::from(tag as u64)), )?; let dense = @@ -106,7 +106,7 @@ impl SpreadVar { dense_row: usize, spread_col: Column, spread_row: usize, - word: Option>, + word: Value>, ) -> Result { let tag = word.map(|word| word.tag); let dense_val = word.map(|word| word.dense); @@ -226,20 +226,20 @@ impl SpreadTableChip { index, || { row = rows.next(); - row.map(|(tag, _, _)| tag).ok_or(Error::Synthesis) + Value::known(row.map(|(tag, _, _)| tag).unwrap()) }, )?; table.assign_cell( || "dense", config.table.dense, index, - || row.map(|(_, dense, _)| dense).ok_or(Error::Synthesis), + || Value::known(row.map(|(_, dense, _)| dense).unwrap()), )?; table.assign_cell( || "spread", config.table.spread, index, - || row.map(|(_, _, spread)| spread).ok_or(Error::Synthesis), + || Value::known(row.map(|(_, _, spread)| spread).unwrap()), )?; } @@ -289,7 +289,7 @@ mod tests { use halo2_proofs::{ arithmetic::FieldExt, - circuit::{Layouter, SimpleFloorPlanner}, + circuit::{Layouter, SimpleFloorPlanner, Value}, dev::MockProver, pairing::bn256::Fr, plonk::{Advice, Circuit, Column, ConstraintSystem, Error}, @@ -331,13 +331,23 @@ mod tests { |mut gate| { let mut row = 0; let mut add_row = |tag, dense, spread| -> Result<(), Error> { - gate.assign_advice(|| "tag", config.input.tag, row, || Ok(tag))?; - gate.assign_advice(|| "dense", config.input.dense, row, || Ok(dense))?; + gate.assign_advice( + || "tag", + config.input.tag, + row, + || Value::known(tag), + )?; + gate.assign_advice( + || "dense", + config.input.dense, + row, + || Value::known(dense), + )?; gate.assign_advice( || "spread", config.input.spread, row, - || Ok(spread), + || Value::known(spread), )?; row += 1; Ok(()) diff --git a/halo2_gadgets/src/sha256/table16/util.rs b/halo2_gadgets/src/sha256/table16/util.rs index 5e609b254f..6a790d3797 100644 --- a/halo2_gadgets/src/sha256/table16/util.rs +++ b/halo2_gadgets/src/sha256/table16/util.rs @@ -1,3 +1,5 @@ +use halo2_proofs::circuit::Value; + pub const MASK_EVEN_32: u32 = 0x55555555; /// The sequence of bits representing a u64 in little-endian order. @@ -17,10 +19,10 @@ pub fn i2lebsp(int: u64) -> [bool; NUM_BITS] { fn gen_const_array_with_default( default_value: Output, - mut closure: impl FnMut(usize) -> Output, + closure: impl FnMut(usize) -> Output, ) -> [Output; LEN] { let mut ret: [Output; LEN] = [default_value; LEN]; - for (bit, val) in ret.iter_mut().zip((0..LEN).map(|idx| closure(idx))) { + for (bit, val) in ret.iter_mut().zip((0..LEN).map(closure)) { *bit = val; } ret @@ -96,25 +98,15 @@ pub fn odd_bits(bits: [bool; LEN]) -> [bool odd_bits } -/// Helper function to transpose an Option> to a Vec>. -/// The length of the vector must be `len`. -pub fn transpose_option_vec(vec: Option>, len: usize) -> Vec> { - if let Some(vec) = vec { - vec.into_iter().map(Some).collect() - } else { - vec![None; len] - } -} - /// Given a vector of words as vec![(lo: u16, hi: u16)], returns their sum: u32, along /// with a carry bit. -pub fn sum_with_carry(words: Vec<(Option, Option)>) -> (Option, Option) { - let words_lo: Option> = words.iter().map(|(lo, _)| lo.map(|lo| lo as u64)).collect(); - let words_hi: Option> = words.iter().map(|(_, hi)| hi.map(|hi| hi as u64)).collect(); +pub fn sum_with_carry(words: Vec<(Value, Value)>) -> (Value, Value) { + let words_lo: Value> = words.iter().map(|(lo, _)| lo.map(|lo| lo as u64)).collect(); + let words_hi: Value> = words.iter().map(|(_, hi)| hi.map(|hi| hi as u64)).collect(); - let sum: Option = { - let sum_lo: Option = words_lo.map(|vec| vec.iter().sum()); - let sum_hi: Option = words_hi.map(|vec| vec.iter().sum()); + let sum: Value = { + let sum_lo: Value = words_lo.map(|vec| vec.iter().sum()); + let sum_hi: Value = words_hi.map(|vec| vec.iter().sum()); sum_lo.zip(sum_hi).map(|(lo, hi)| lo + (1 << 16) * hi) }; diff --git a/halo2_proofs/CHANGELOG.md b/halo2_proofs/CHANGELOG.md index 76ccbb0416..a70020222b 100644 --- a/halo2_proofs/CHANGELOG.md +++ b/halo2_proofs/CHANGELOG.md @@ -6,50 +6,173 @@ and this project adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [0.2.0] - 2022-06-23 +### Added +- `halo2_proofs::circuit::Value`, a more usable and type-safe replacement for + `Option` in circuit synthesis. +- `impl Mul for &Assigned` + +### Changed +All APIs that represented witnessed values as `Option` now represent them as +`halo2_proofs::circuit::Value`. The core API changes are listed below. + +- The following APIs now take `Value<_>` instead of `Option<_>`: + - `halo2_proofs::plonk`: + - `Assignment::fill_from_row` +- The following APIs now take value closures that return `Value` instead of + `Result`: + - `halo2_proofs::circuit`: + - `Region::{assign_advice, assign_fixed}` + - `Table::assign_cell` + - `halo2_proofs::circuit::layouter`: + - `RegionLayouter::{assign_advice, assign_fixed}` + - `TableLayouter::assign_cell` + - `halo2_proofs::plonk`: + - `Assignment::{assign_advice, assign_fixed}` +- The following APIs now return `Value<_>` instead of `Option<_>`: + - `halo2_proofs::circuit`: + - `AssignedCell::{value, value_field}` +- The following APIs now return `Result, Error>` instead of + `Result, Error>`: + - `halo2_proofs::plonk`: + - `Assignment::query_instance` +- The following APIs now return `Result<(Cell, Value), Error>` instead of + `Result<(Cell, Option), Error>`: + - `halo2_proofs::circuit::layouter`: + - `RegionLayouter::assign_advice_from_instance` +- `halo2_proofs::plonk::BatchVerifier` has been rewritten. It is no longer a + verification strategy to be used with `verify_proof`, but instead manages the + entire batch verification process. The `batch` crate feature (enabled by + default) must be enabled to use the batch verifier. + +## [0.1.0] - 2022-05-10 +### Added +- `halo2_proofs::dev`: + - `MockProver::assert_satisfied`, for requiring that a circuit is satisfied. + It panics like `assert_eq!(mock_prover.verify(), Ok(()))`, but pretty-prints + any verification failures before panicking. +- `halo2_proofs::plonk::Constraints` helper, for constructing a gate from a set + of constraints with a common selector. + +### Changed +- `halo2_proofs::dev`: + - `VerifyFailure::CellNotAssigned` now has a `gate_offset` field, storing the + offset in the region at which the gate queries the cell that needs to be + assigned. + - The `row` field of `VerifyFailure::Permutation` has been replaced by a + `location` field, which can now indicate whether the location falls within + an assigned region. + +## [0.1.0-beta.4] - 2022-04-06 +### Changed +- PLONK prover was improved to avoid stack overflows when large numbers of gates + are involved in a proof. + +## [0.1.0-beta.3] - 2022-03-22 +### Added +- `halo2_proofs::circuit`: + - `AssignedCell::, F>::evaluate -> AssignedCell` + - `Assigned::{is_zero_vartime, double, square, cube}` + - Various trait impls for `Assigned`: + - `From<&Assigned>` + - `PartialEq, Eq` + - `Add<&Assigned>, AddAssign, AddAssign<&Assigned>` + - `Sub<&Assigned>, SubAssign, SubAssign<&Assigned>` + - `Mul<&Assigned>, MulAssign, MulAssign<&Assigned>` + +### Removed +- `halo2_proofs::plonk::VerifyingKey::{read, write}` (for details see + [issue 449](https://github.com/zcash/halo2/issues/449)) + +## [0.1.0-beta.2] - 2022-02-14 (relative to `halo2 0.1.0-beta.1`) ### Added +- `halo2_proofs::circuit::AssignedCell`, an abstraction for typed `Cell`s that + track the type (and witnessed value if known) of the assignment. - `halo2_proofs::plonk`: - `VerificationStrategy` - `SingleVerifier`, an implementation of `VerificationStrategy` for verifying proofs individually. - `BatchVerifier`, an implementation of `VerificationStrategy` for verifying multiple proofs in a batch. -- `halo2_proofs::dev::FailureLocation` (used in `VerifyFailure::Lookup`) + - `Column::column_type` + - `impl {PartialOrd, Ord} for Any` + - `Error::ColumnNotInPermutation` +- `halo2_proofs::poly::Basis: Copy` bound, and corresponding implementations for + the provided bases. +- `halo2_proofs::dev`: + - `FailureLocation` (used in `VerifyFailure::Lookup`) + - `metadata::VirtualCell` (used in `VerifyFailure::ConstraintNotSatisfied`) + - `impl From<(usize, &str)> for metadata::Region` + +### Fixed +- `halo2_proofs::plonk::Assigned` addition was producing incorrect results in + some cases due to how the deferred representation of `inv0` was handled. This + could not cause a soundness error, because `Assigned` is only used during + witness generation, not when defining constraints. However, it did mean that + the prover would fail to create a valid proof for some subset of valid + witnesses. [Fixed in #423](https://github.com/zcash/halo2/issues/423). ### Changed -- `halo2_proofs::plonk::verify_proof` now takes a `VerificationStrategy` instead - of an `MSM` directly. -- `halo2_proofs` now depends on `rand_core` instead of `rand`. -- `halo2_proofs::plonk::create_proof` now take an argument `R: rand_core::RngCore`. -- `halo2_proofs::plonk::Error` has been overhauled: - - `Error` now implements `std::fmt::Display` and `std::error::Error`. - - `Error` no longer implements `PartialEq`. Tests can check for specific error - cases with `assert!(matches!(..))`, or the `assert_matches` crate. - - `Error::IncompatibleParams` is now `Error::InvalidInstances`. - - `Error::NotEnoughRowsAvailable` now stores the current value of `k`. - - `Error::OpeningError` is now `Error::Opening`. - - `Error::SynthesisError` is now `Error::Synthesis`. - - `Error::TranscriptError` is now `Error::Transcript`, and stores the - underlying `io::Error`. -- `halo2_proofs::dev::CircuitLayout::render` now takes `k` as a `u32`, matching - the regular parameter APIs. -- `halo2_proofs::dev::VerifyFailure` has been overhauled: - - `VerifyFailure::Cell` has been renamed to `VerifyFailure::CellNotAssigned`. - - `VerifyFailure::ConstraintNotSatisfied` now has a `cell_values` field, - storing the values of the cells used in the unsatisfied constraint. - - The `row` fields of `VerifyFailure::{ConstraintNotSatisfied, Lookup}` have - been replaced by `location` fields, which can now indicate whether the - location falls within an assigned region. -- `halo2_proofs::plonk::ConstraintSystem::enable_equality` and - `halo2_proofs::plonk::ConstraintSystem::query_any` now take `Into>` - instead of `Column` as a parameter to avoid excesive `.into()` usage. +- Migrated to `rand_core` (instead of `rand`), `pasta_curves 0.3`. +- `halo2_proofs::circuit`: + - `Region` now returns `AssignedCell` instead of `Cell` or `(Cell, Option)` + from its assignment APIs, and the result types `VR` of their value closures + now have the bound `for<'vr> Assigned: From<&'vr VR>` instead of + `VR: Into>`: + - `assign_advice` + - `assign_advice_from_constant` + - `assign_advice_from_instance` + - `assign_fixed` +- `halo2_proofs::plonk`: + - `create_proof` now take an argument `R: rand_core::RngCore`. + - `verify_proof` now takes a `VerificationStrategy` instead of an `MSM` + directly, and returns `VerificationStrategy::Output` instead of `Guard`. + - `ConstraintSystem::enable_equality` and `ConstraintSystem::query_any` now + take `Into>` instead of `Column` as a parameter to avoid + excesive `.into()` usage. + - `Error` has been overhauled: + - `Error` now implements `std::fmt::Display` and `std::error::Error`. + - `Error` no longer implements `PartialEq`. Tests can check for specific + error cases with `assert!(matches!(..))`, or the `assert_matches` crate. + - `Error::IncompatibleParams` is now `Error::InvalidInstances`. + - `Error::NotEnoughRowsAvailable` now stores the current value of `k`. + - `Error::OpeningError` is now `Error::Opening`. + - `Error::SynthesisError` is now `Error::Synthesis`. + - `Error::TranscriptError` is now `Error::Transcript`, and stores the + underlying `io::Error`. +- `halo2_proofs::poly`: + - `commitment::Accumulator` had its `challenges_packed` field renamed to + `u_packed`. + - `commitment::Guard`, returned by the closure passed into + `VerificationStrategy::process` (and previously returned from `verify_proof` + directly), has changed so that values returned from its method `compute_g` + and expected by its method `use_g` are **NOT backwards compatible** with + values in previous version (namely `halo2 0.1.0-beta.1`). + - `commitment::MSM::add_to_h_scalar` was renamed to `MSM::add_to_w_scalar`. + - `commitment::create_proof` now take an argument `R: rand_core::RngCore`. + - `multiopen::create_proof` now take an argument `R: rand_core::RngCore`. +- `halo2_proofs::dev`: + - `CircuitLayout::render` now takes `k` as a `u32`, matching the regular + parameter APIs. + - `VerifyFailure` has been overhauled: + - `VerifyFailure::Cell` has been renamed to `VerifyFailure::CellNotAssigned`. + - `VerifyFailure::ConstraintNotSatisfied` now has a `cell_values` field, + storing the values of the cells used in the unsatisfied constraint. + - The `row` fields of `VerifyFailure::{ConstraintNotSatisfied, Lookup}` have + been replaced by `location` fields, which can now indicate whether the + location falls within an assigned region. ### Removed -- `halo2_proofs::arithmetic::BatchInvert` (use `ff::BatchInvert` instead). -- `impl Default for halo2_proofs::poly::Rotation` (use `Rotation::cur()` instead). +- `halo2_proofs::arithmetic`: + - `BatchInvert` (use `ff::BatchInvert` instead). + - Several parts of the `pasta_curves::arithmetic` API that were re-exported + here (see the changelog for `pasta_curves 0.3.0` for details). - `halo2_proofs::poly`: - `EvaluationDomain::{add_extended, sub_extended, mul_extended}` - `Polynomial::one_minus` - `impl Neg, Sub for Polynomial` - `impl Mul for Polynomial<_, ExtendedLagrangeCoeff>` + - `impl Default for Rotation` (use `Rotation::cur()` instead). diff --git a/halo2_proofs/Cargo.toml b/halo2_proofs/Cargo.toml index a0ea8a1cb0..7e46b48845 100644 --- a/halo2_proofs/Cargo.toml +++ b/halo2_proofs/Cargo.toml @@ -1,24 +1,27 @@ [package] name = "halo2_proofs" -version = "0.1.0-beta.1" +version = "0.2.0" authors = [ "Sean Bowe ", "Ying Tong Lai ", "Daira Hopwood ", "Jack Grigg ", ] -edition = "2018" +edition = "2021" +rust-version = "1.56.1" description = """ -[BETA] Fast proof-carrying data implementation with no trusted setup +Fast PLONK-based zero-knowledge proving system with no trusted setup """ -license-file = "../COPYING" +license = "MIT OR Apache-2.0" repository = "https://github.com/zcash/halo2" documentation = "https://docs.rs/halo2_proofs" readme = "README.md" +categories = ["cryptography"] +keywords = ["halo", "proofs", "zkp", "zkSNARKs"] [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "../katex-header.html"] +rustdoc-args = ["--cfg", "docsrs", "--html-in-header", "katex-header.html"] [[bench]] name = "arithmetic" @@ -28,13 +31,22 @@ harness = false name = "plonk" harness = false +[[bench]] +name = "dev_lookup" +harness = false + +[[bench]] +name = "fft" +harness = false + [dependencies] backtrace = { version = "0.3", optional = true } rayon = "1.5.1" -ff = "0.11" -group = "0.11" +ff = "0.12" +group = "0.12" rand = "0.8" rand_core = { version = "0.6", default-features = false } +tracing = "0.1" blake2b_simd = "1" pairing = { git = 'https://github.com/appliedzkp/pairing', package = "pairing_bn256", "tag" = "v0.1.1"} subtle = "2.3" @@ -44,13 +56,6 @@ cfg-if = "0.1" plotters = { version = "0.3.0", optional = true } tabbycat = { version = "0.1", features = ["attributes"], optional = true } -[target.'cfg(target_arch = "wasm32")'.dependencies] -# plotters depends on web-sys, which eventually depends on bumpalo 3. This dependency is -# required because our MSRV is 1.51, but bumpalo 3.9 increased its MSRV to 1.54. We can -# remove this once our MSRV is 1.54+ (and should do so, because currently this makes it a -# required dependency even if the dev-graph feature flag is not enabled). -bumpalo = ">=3,<3.9.0" - [dev-dependencies] assert_matches = "1.5" criterion = "0.3" diff --git a/halo2_proofs/README.md b/halo2_proofs/README.md index e63bd9110b..7c226ff24c 100644 --- a/halo2_proofs/README.md +++ b/halo2_proofs/README.md @@ -1,12 +1,10 @@ # halo2_proofs [![Crates.io](https://img.shields.io/crates/v/halo2_proofs.svg)](https://crates.io/crates/halo2_proofs) # -**IMPORTANT**: This library is in beta, and should not be used in production software. - ## [Documentation](https://docs.rs/halo2_proofs) ## Minimum Supported Rust Version -Requires Rust **1.51** or higher. +Requires Rust **1.56.1** or higher. Minimum supported Rust version can be changed in the future, but it will be done with a minor version bump. @@ -19,14 +17,17 @@ threads. ## License -Copyright 2020-2021 The Electric Coin Company. +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. -You may use this package under the Bootstrap Open Source Licence, version 1.0, -or at your option, any later version. See the file [`COPYING`](COPYING) for -more details, and [`LICENSE-BOSL`](LICENSE-BOSL) for the terms of the Bootstrap -Open Source Licence, version 1.0. +### Contribution -The purpose of the BOSL is to allow commercial improvements to the package -while ensuring that all improvements are open source. See -[here](https://electriccoin.co/blog/introducing-tgppl-a-radically-new-type-of-open-source-license/) -for why the BOSL exists. +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/halo2_proofs/benches/dev_lookup.rs b/halo2_proofs/benches/dev_lookup.rs new file mode 100644 index 0000000000..7592b49812 --- /dev/null +++ b/halo2_proofs/benches/dev_lookup.rs @@ -0,0 +1,114 @@ +#[macro_use] +extern crate criterion; + +use halo2_proofs::arithmetic::FieldExt; +use halo2_proofs::circuit::{Layouter, SimpleFloorPlanner, Value}; +use halo2_proofs::dev::MockProver; +use halo2_proofs::plonk::*; +use halo2_proofs::poly::Rotation; +use pasta_curves::pallas; + +use std::marker::PhantomData; + +use criterion::{BenchmarkId, Criterion}; + +fn criterion_benchmark(c: &mut Criterion) { + #[derive(Clone, Default)] + struct MyCircuit { + _marker: PhantomData, + } + + #[derive(Clone)] + struct MyConfig { + selector: Selector, + table: TableColumn, + advice: Column, + } + + impl Circuit for MyCircuit { + type Config = MyConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> MyConfig { + let config = MyConfig { + selector: meta.complex_selector(), + table: meta.lookup_table_column(), + advice: meta.advice_column(), + }; + + meta.lookup(|meta| { + let selector = meta.query_selector(config.selector); + let not_selector = Expression::Constant(F::one()) - selector.clone(); + let advice = meta.query_advice(config.advice, Rotation::cur()); + vec![(selector * advice + not_selector, config.table)] + }); + + config + } + + fn synthesize( + &self, + config: MyConfig, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_table( + || "8-bit table", + |mut table| { + for row in 0u64..(1 << 8) { + table.assign_cell( + || format!("row {}", row), + config.table, + row as usize, + || Value::known(F::from(row + 1)), + )?; + } + + Ok(()) + }, + )?; + + layouter.assign_region( + || "assign values", + |mut region| { + for offset in 0u64..(1 << 10) { + config.selector.enable(&mut region, offset as usize)?; + region.assign_advice( + || format!("offset {}", offset), + config.advice, + offset as usize, + || Value::known(F::from((offset % 256) + 1)), + )?; + } + + Ok(()) + }, + ) + } + } + + fn prover(k: u32) { + let circuit = MyCircuit:: { + _marker: PhantomData, + }; + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } + + let k_range = 14..=18; + + let mut prover_group = c.benchmark_group("dev-lookup"); + prover_group.sample_size(10); + for k in k_range { + prover_group.bench_with_input(BenchmarkId::from_parameter(k), &k, |b, &k| { + b.iter(|| prover(k)); + }); + } + prover_group.finish(); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/halo2_proofs/benches/fft.rs b/halo2_proofs/benches/fft.rs new file mode 100644 index 0000000000..d9564958e2 --- /dev/null +++ b/halo2_proofs/benches/fft.rs @@ -0,0 +1,26 @@ +#[macro_use] +extern crate criterion; + +use crate::arithmetic::best_fft; +use crate::pasta::Fp; +use group::ff::Field; +use halo2_proofs::*; + +use criterion::{BenchmarkId, Criterion}; +use rand_core::OsRng; + +fn criterion_benchmark(c: &mut Criterion) { + let mut group = c.benchmark_group("fft"); + for k in 3..19 { + group.bench_function(BenchmarkId::new("k", k), |b| { + let mut a = (0..(1 << k)).map(|_| Fp::random(OsRng)).collect::>(); + let omega = Fp::random(OsRng); // would be weird if this mattered + b.iter(|| { + best_fft(&mut a, omega, k as u32); + }); + }); + } +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/halo2_proofs/benches/plonk.rs b/halo2_proofs/benches/plonk.rs index ef2c0e76a1..074b8881cd 100644 --- a/halo2_proofs/benches/plonk.rs +++ b/halo2_proofs/benches/plonk.rs @@ -3,7 +3,7 @@ extern crate criterion; use group::ff::Field; use halo2_proofs::arithmetic::FieldExt; -use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner}; +use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; use halo2_proofs::pairing::bn256::{Bn256, Fr as Fp, G1Affine}; use halo2_proofs::plonk::*; use halo2_proofs::poly::{ @@ -41,20 +41,20 @@ fn criterion_benchmark(c: &mut Criterion) { f: F, ) -> Result<(Cell, Cell, Cell), Error> where - F: FnMut() -> Result<(FF, FF, FF), Error>; + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; fn raw_add( &self, layouter: &mut impl Layouter, f: F, ) -> Result<(Cell, Cell, Cell), Error> where - F: FnMut() -> Result<(FF, FF, FF), Error>; + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; } #[derive(Clone)] struct MyCircuit { - a: Option, + a: Value, k: u32, } @@ -79,7 +79,7 @@ fn criterion_benchmark(c: &mut Criterion) { mut f: F, ) -> Result<(Cell, Cell, Cell), Error> where - F: FnMut() -> Result<(FF, FF, FF), Error>, + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, { layouter.assign_region( || "raw_multiply", @@ -90,27 +90,32 @@ fn criterion_benchmark(c: &mut Criterion) { self.config.a, 0, || { - value = Some(f()?); - Ok(value.ok_or(Error::Synthesis)?.0) + value = Some(f()); + value.unwrap().map(|v| v.0) }, )?; let rhs = region.assign_advice( || "rhs", self.config.b, 0, - || Ok(value.ok_or(Error::Synthesis)?.1), + || value.unwrap().map(|v| v.1), )?; let out = region.assign_advice( || "out", self.config.c, 0, - || Ok(value.ok_or(Error::Synthesis)?.2), + || value.unwrap().map(|v| v.2), )?; - region.assign_fixed(|| "a", self.config.sa, 0, || Ok(FF::zero()))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Ok(FF::zero()))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Ok(FF::one()))?; - region.assign_fixed(|| "a * b", self.config.sm, 0, || Ok(FF::one()))?; + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::zero()))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::zero()))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?; + region.assign_fixed( + || "a * b", + self.config.sm, + 0, + || Value::known(FF::one()), + )?; Ok((lhs.cell(), rhs.cell(), out.cell())) }, ) @@ -121,7 +126,7 @@ fn criterion_benchmark(c: &mut Criterion) { mut f: F, ) -> Result<(Cell, Cell, Cell), Error> where - F: FnMut() -> Result<(FF, FF, FF), Error>, + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, { layouter.assign_region( || "raw_add", @@ -132,27 +137,32 @@ fn criterion_benchmark(c: &mut Criterion) { self.config.a, 0, || { - value = Some(f()?); - Ok(value.ok_or(Error::Synthesis)?.0) + value = Some(f()); + value.unwrap().map(|v| v.0) }, )?; let rhs = region.assign_advice( || "rhs", self.config.b, 0, - || Ok(value.ok_or(Error::Synthesis)?.1), + || value.unwrap().map(|v| v.1), )?; let out = region.assign_advice( || "out", self.config.c, 0, - || Ok(value.ok_or(Error::Synthesis)?.2), + || value.unwrap().map(|v| v.2), )?; - region.assign_fixed(|| "a", self.config.sa, 0, || Ok(FF::one()))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Ok(FF::one()))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Ok(FF::one()))?; - region.assign_fixed(|| "a * b", self.config.sm, 0, || Ok(FF::zero()))?; + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::one()))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::one()))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?; + region.assign_fixed( + || "a * b", + self.config.sm, + 0, + || Value::known(FF::zero()), + )?; Ok((lhs.cell(), rhs.cell(), out.cell())) }, ) @@ -172,7 +182,10 @@ fn criterion_benchmark(c: &mut Criterion) { type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { - Self { a: None, k: self.k } + Self { + a: Value::unknown(), + k: self.k, + } } fn configure(meta: &mut ConstraintSystem) -> PlonkConfig { @@ -223,22 +236,17 @@ fn criterion_benchmark(c: &mut Criterion) { let cs = StandardPlonk::new(config); for _ in 0..((1 << (self.k - 1)) - 3) { - let mut a_squared = None; + let a: Value> = self.a.into(); + let mut a_squared = Value::unknown(); let (a0, _, c0) = cs.raw_multiply(&mut layouter, || { - a_squared = self.a.map(|a| a.square()); - Ok(( - self.a.ok_or(Error::Synthesis)?, - self.a.ok_or(Error::Synthesis)?, - a_squared.ok_or(Error::Synthesis)?, - )) + a_squared = a.square(); + a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) })?; let (a1, b1, _) = cs.raw_add(&mut layouter, || { - let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2)); - Ok(( - self.a.ok_or(Error::Synthesis)?, - a_squared.ok_or(Error::Synthesis)?, - fin.ok_or(Error::Synthesis)?, - )) + let fin = a_squared + a; + a.zip(a_squared) + .zip(fin) + .map(|((a, a_squared), fin)| (a, a_squared, fin)) })?; cs.copy(&mut layouter, a0, a1)?; cs.copy(&mut layouter, b1, c0)?; @@ -258,7 +266,7 @@ fn criterion_benchmark(c: &mut Criterion) { let params: Params = Params::::unsafe_setup::(k); let params_verifier: ParamsVerifier = params.verifier(0).unwrap(); - let empty_circuit: MyCircuit = MyCircuit { a: None, k }; + let empty_circuit: MyCircuit = MyCircuit { a: Value::unknown(), k }; let vk = keygen_vk(¶ms, &empty_circuit).expect("keygen_vk should not fail"); let pk = keygen_pk(¶ms, vk, &empty_circuit).expect("keygen_pk should not fail"); (params, params_verifier, pk) @@ -268,7 +276,7 @@ fn criterion_benchmark(c: &mut Criterion) { let rng = OsRng; let circuit: MyCircuit = MyCircuit { - a: Some(Fp::random(rng)), + a: Value::known(Fp::random(rng)), k, }; diff --git a/halo2_proofs/examples/circuit-layout.rs b/halo2_proofs/examples/circuit-layout.rs index 921d96298f..98b9ea67b6 100644 --- a/halo2_proofs/examples/circuit-layout.rs +++ b/halo2_proofs/examples/circuit-layout.rs @@ -1,9 +1,9 @@ use ff::Field; use halo2_proofs::{ arithmetic::FieldExt, - circuit::{Cell, Layouter, Region, SimpleFloorPlanner}, + circuit::{Cell, Layouter, Region, SimpleFloorPlanner, Value}, pairing::bn256::Fr as Fp, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, TableColumn}, + plonk::{Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, TableColumn}, poly::Rotation, }; use rand_core::OsRng; @@ -31,16 +31,16 @@ struct PlonkConfig { trait StandardCs { fn raw_multiply(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> where - F: FnMut() -> Result<(FF, FF, FF), Error>; + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; fn raw_add(&self, region: &mut Region, f: F) -> Result<(Cell, Cell, Cell), Error> where - F: FnMut() -> Result<(FF, FF, FF), Error>; + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; fn copy(&self, region: &mut Region, a: Cell, b: Cell) -> Result<(), Error>; fn lookup_table(&self, layouter: &mut impl Layouter, values: &[FF]) -> Result<(), Error>; } struct MyCircuit { - a: Option, + a: Value, lookup_table: Vec, } @@ -65,7 +65,7 @@ impl StandardCs for StandardPlonk { mut f: F, ) -> Result<(Cell, Cell, Cell), Error> where - F: FnMut() -> Result<(FF, FF, FF), Error>, + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, { let mut value = None; let lhs = region.assign_advice( @@ -73,44 +73,36 @@ impl StandardCs for StandardPlonk { self.config.a, 0, || { - value = Some(f()?); - Ok(value.ok_or(Error::Synthesis)?.0) + value = Some(f()); + value.unwrap().map(|v| v.0) }, )?; region.assign_advice( || "lhs^4", self.config.d, 0, - || Ok(value.ok_or(Error::Synthesis)?.0.square().square()), - )?; - let rhs = region.assign_advice( - || "rhs", - self.config.b, - 0, - || Ok(value.ok_or(Error::Synthesis)?.1), + || value.unwrap().map(|v| v.0).square().square(), )?; + let rhs = + region.assign_advice(|| "rhs", self.config.b, 0, || value.unwrap().map(|v| v.1))?; region.assign_advice( || "rhs^4", self.config.e, 0, - || Ok(value.ok_or(Error::Synthesis)?.1.square().square()), - )?; - let out = region.assign_advice( - || "out", - self.config.c, - 0, - || Ok(value.ok_or(Error::Synthesis)?.2), + || value.unwrap().map(|v| v.1).square().square(), )?; + let out = + region.assign_advice(|| "out", self.config.c, 0, || value.unwrap().map(|v| v.2))?; - region.assign_fixed(|| "a", self.config.sa, 0, || Ok(FF::zero()))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Ok(FF::zero()))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Ok(FF::one()))?; - region.assign_fixed(|| "a * b", self.config.sm, 0, || Ok(FF::one()))?; + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::zero()))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::zero()))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?; + region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::one()))?; Ok((lhs.cell(), rhs.cell(), out.cell())) } fn raw_add(&self, region: &mut Region, mut f: F) -> Result<(Cell, Cell, Cell), Error> where - F: FnMut() -> Result<(FF, FF, FF), Error>, + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, { let mut value = None; let lhs = region.assign_advice( @@ -118,39 +110,31 @@ impl StandardCs for StandardPlonk { self.config.a, 0, || { - value = Some(f()?); - Ok(value.ok_or(Error::Synthesis)?.0) + value = Some(f()); + value.unwrap().map(|v| v.0) }, )?; region.assign_advice( || "lhs^4", self.config.d, 0, - || Ok(value.ok_or(Error::Synthesis)?.0.square().square()), - )?; - let rhs = region.assign_advice( - || "rhs", - self.config.b, - 0, - || Ok(value.ok_or(Error::Synthesis)?.1), + || value.unwrap().map(|v| v.0.square().square()), )?; + let rhs = + region.assign_advice(|| "rhs", self.config.b, 0, || value.unwrap().map(|v| v.1))?; region.assign_advice( || "rhs^4", self.config.e, 0, - || Ok(value.ok_or(Error::Synthesis)?.1.square().square()), - )?; - let out = region.assign_advice( - || "out", - self.config.c, - 0, - || Ok(value.ok_or(Error::Synthesis)?.2), + || value.unwrap().map(|v| v.1.square().square()), )?; + let out = + region.assign_advice(|| "out", self.config.c, 0, || value.unwrap().map(|v| v.2))?; - region.assign_fixed(|| "a", self.config.sa, 0, || Ok(FF::one()))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Ok(FF::one()))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Ok(FF::one()))?; - region.assign_fixed(|| "a * b", self.config.sm, 0, || Ok(FF::zero()))?; + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::one()))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::one()))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?; + region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::zero()))?; Ok((lhs.cell(), rhs.cell(), out.cell())) } fn copy(&self, region: &mut Region, left: Cell, right: Cell) -> Result<(), Error> { @@ -161,7 +145,12 @@ impl StandardCs for StandardPlonk { || "", |mut table| { for (index, &value) in values.iter().enumerate() { - table.assign_cell(|| "table col", self.config.sl, index, || Ok(value))?; + table.assign_cell( + || "table col", + self.config.sl, + index, + || Value::known(value), + )?; } Ok(()) }, @@ -176,7 +165,7 @@ impl Circuit for MyCircuit { fn without_witnesses(&self) -> Self { Self { - a: None, + a: Value::unknown(), lookup_table: self.lookup_table.clone(), } } @@ -257,22 +246,17 @@ impl Circuit for MyCircuit { layouter.assign_region( || format!("region_{}", i), |mut region| { - let mut a_squared = None; + let a: Value> = self.a.into(); + let mut a_squared = Value::unknown(); let (a0, _, c0) = cs.raw_multiply(&mut region, || { - a_squared = self.a.map(|a| a.square()); - Ok(( - self.a.ok_or(Error::Synthesis)?, - self.a.ok_or(Error::Synthesis)?, - a_squared.ok_or(Error::Synthesis)?, - )) + a_squared = a.square(); + a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) })?; let (a1, b1, _) = cs.raw_add(&mut region, || { - let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2)); - Ok(( - self.a.ok_or(Error::Synthesis)?, - a_squared.ok_or(Error::Synthesis)?, - fin.ok_or(Error::Synthesis)?, - )) + let fin = a_squared + a; + a.zip(a_squared) + .zip(fin) + .map(|((a, a_squared), fin)| (a, a_squared, fin)) })?; cs.copy(&mut region, a0, a1)?; cs.copy(&mut region, b1, c0) @@ -294,7 +278,7 @@ fn main() { let instance = Fp::one() + Fp::one(); let lookup_table = vec![instance, a, a, Fp::zero()]; let circuit: MyCircuit = MyCircuit { - a: None, + a: Value::unknown(), lookup_table, }; diff --git a/halo2_proofs/examples/cost-model.rs b/halo2_proofs/examples/cost-model.rs index 4d55553734..85f161ad04 100644 --- a/halo2_proofs/examples/cost-model.rs +++ b/halo2_proofs/examples/cost-model.rs @@ -100,8 +100,7 @@ impl FromStr for Poly { #[derive(Debug)] struct Lookup { - #[allow(dead_code)] - columns: usize, + _columns: usize, input_deg: usize, table_deg: usize, } @@ -111,11 +110,11 @@ impl FromStr for Lookup { fn from_str(s: &str) -> Result { let mut parts = s.split(','); - let columns = parts.next().unwrap().parse()?; + let _columns = parts.next().unwrap().parse()?; let input_deg = parts.next().unwrap().parse()?; let table_deg = parts.next().unwrap().parse()?; Ok(Lookup { - columns, + _columns, input_deg, table_deg, }) @@ -124,7 +123,7 @@ impl FromStr for Lookup { impl Lookup { fn required_degree(&self) -> usize { - 1 + cmp::max(1, self.input_deg) + cmp::max(1, self.table_deg) + 2 + cmp::max(1, self.input_deg) + cmp::max(1, self.table_deg) } fn queries(&self) -> impl Iterator { @@ -209,8 +208,8 @@ impl From for Circuit { .chain(opts.instance.iter()) .chain(opts.fixed.iter()) .cloned() - .chain(opts.lookup.iter().map(|l| l.queries()).flatten()) - .chain(opts.permutation.iter().map(|p| p.queries()).flatten()) + .chain(opts.lookup.iter().flat_map(|l| l.queries())) + .chain(opts.permutation.iter().flat_map(|p| p.queries())) .chain(iter::repeat("0".parse().unwrap()).take(max_deg - 1)) .collect(); diff --git a/halo2_proofs/examples/simple-example.rs b/halo2_proofs/examples/simple-example.rs index 5e76fb21f1..83e552ec88 100644 --- a/halo2_proofs/examples/simple-example.rs +++ b/halo2_proofs/examples/simple-example.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use halo2_proofs::{ arithmetic::FieldExt, - circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner}, + circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector}, poly::Rotation, }; @@ -13,7 +13,7 @@ trait NumericInstructions: Chip { type Num; /// Loads a number into the circuit as a private input. - fn load_private(&self, layouter: impl Layouter, a: Option) -> Result; + fn load_private(&self, layouter: impl Layouter, a: Value) -> Result; /// Loads a number into the circuit as a fixed constant. fn load_constant(&self, layouter: impl Layouter, constant: F) -> Result; @@ -151,7 +151,7 @@ impl NumericInstructions for FieldChip { fn load_private( &self, mut layouter: impl Layouter, - value: Option, + value: Value, ) -> Result { let config = self.config(); @@ -159,12 +159,7 @@ impl NumericInstructions for FieldChip { || "load private", |mut region| { region - .assign_advice( - || "private input", - config.advice[0], - 0, - || value.ok_or(Error::Synthesis), - ) + .assign_advice(|| "private input", config.advice[0], 0, || value) .map(Number) }, ) @@ -212,17 +207,12 @@ impl NumericInstructions for FieldChip { // Now we can assign the multiplication result, which is to be assigned // into the output position. - let value = a.0.value().and_then(|a| b.0.value().map(|b| *a * *b)); + let value = a.0.value().copied() * b.0.value(); // Finally, we do the assignment to the output, returning a // variable to be used in another part of the circuit. region - .assign_advice( - || "lhs * rhs", - config.advice[0], - 1, - || value.ok_or(Error::Synthesis), - ) + .assign_advice(|| "lhs * rhs", config.advice[0], 1, || value) .map(Number) }, ) @@ -250,8 +240,8 @@ impl NumericInstructions for FieldChip { #[derive(Default)] struct MyCircuit { constant: F, - a: Option, - b: Option, + a: Value, + b: Value, } impl Circuit for MyCircuit { @@ -329,8 +319,8 @@ fn main() { // Instantiate the circuit with the private inputs. let circuit = MyCircuit { constant, - a: Some(a), - b: Some(b), + a: Value::known(a), + b: Value::known(b), }; // Arrange the public input. We expose the multiplication result in row 0 diff --git a/halo2_proofs/examples/two-chip.rs b/halo2_proofs/examples/two-chip.rs index f9a7d0c8a6..3f024690e4 100644 --- a/halo2_proofs/examples/two-chip.rs +++ b/halo2_proofs/examples/two-chip.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use halo2_proofs::{ arithmetic::FieldExt, - circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner}, + circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value}, plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector}, poly::Rotation, }; @@ -20,7 +20,7 @@ trait FieldInstructions: AddInstructions + MulInstructions { fn load_private( &self, layouter: impl Layouter, - a: Option, + a: Value, ) -> Result<>::Num, Error>; /// Returns `d = (a + b) * c`. @@ -217,17 +217,12 @@ impl AddInstructions for AddChip { // Now we can compute the addition result, which is to be assigned // into the output position. - let value = a.0.value().and_then(|a| b.0.value().map(|b| *a + *b)); + let value = a.0.value().copied() + b.0.value(); // Finally, we do the assignment to the output, returning a // variable to be used in another part of the circuit. region - .assign_advice( - || "lhs + rhs", - config.advice[0], - 1, - || value.ok_or(Error::Synthesis), - ) + .assign_advice(|| "lhs + rhs", config.advice[0], 1, || value) .map(Number) }, ) @@ -343,17 +338,12 @@ impl MulInstructions for MulChip { // Now we can compute the multiplication result, which is to be assigned // into the output position. - let value = a.0.value().and_then(|a| b.0.value().map(|b| *a * *b)); + let value = a.0.value().copied() * b.0.value(); // Finally, we do the assignment to the output, returning a // variable to be used in another part of the circuit. region - .assign_advice( - || "lhs * rhs", - config.advice[0], - 1, - || value.ok_or(Error::Synthesis), - ) + .assign_advice(|| "lhs * rhs", config.advice[0], 1, || value) .map(Number) }, ) @@ -412,7 +402,7 @@ impl FieldInstructions for FieldChip { fn load_private( &self, mut layouter: impl Layouter, - value: Option, + value: Value, ) -> Result<>::Num, Error> { let config = self.config(); @@ -420,12 +410,7 @@ impl FieldInstructions for FieldChip { || "load private", |mut region| { region - .assign_advice( - || "private input", - config.advice[0], - 0, - || value.ok_or(Error::Synthesis), - ) + .assign_advice(|| "private input", config.advice[0], 0, || value) .map(Number) }, ) @@ -459,14 +444,14 @@ impl FieldInstructions for FieldChip { // ANCHOR: circuit /// The full circuit implementation. /// -/// In this struct we store the private input variables. We use `Option` because +/// In this struct we store the private input variables. We use `Value` because /// they won't have any value during key generation. During proving, if any of these -/// were `None` we would get an error. +/// were `Value::unknown()` we would get an error. #[derive(Default)] struct MyCircuit { - a: Option, - b: Option, - c: Option, + a: Value, + b: Value, + c: Value, } impl Circuit for MyCircuit { @@ -530,9 +515,9 @@ fn main() { // Instantiate the circuit with the private inputs. let circuit = MyCircuit { - a: Some(a), - b: Some(b), - c: Some(c), + a: Value::known(a), + b: Value::known(b), + c: Value::known(c), }; // Arrange the public input. We expose the multiplication result in row 0 diff --git a/halo2_proofs/katex-header.html b/halo2_proofs/katex-header.html new file mode 100644 index 0000000000..98e85904fa --- /dev/null +++ b/halo2_proofs/katex-header.html @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/halo2_proofs/src/arithmetic.rs b/halo2_proofs/src/arithmetic.rs index f442223edb..64c6f63247 100644 --- a/halo2_proofs/src/arithmetic.rs +++ b/halo2_proofs/src/arithmetic.rs @@ -169,18 +169,7 @@ pub fn best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Cu /// /// This will use multithreading if beneficial. pub fn best_fft(a: &mut [G], omega: G::Scalar, log_n: u32) { - let threads = multicore::current_num_threads(); - let log_threads = log2_floor(threads); - - if log_n <= log_threads { - serial_fft(a, omega, log_n); - } else { - parallel_fft(a, omega, log_n, log_threads); - } -} - -fn serial_fft(a: &mut [G], omega: G::Scalar, log_n: u32) { - fn bitreverse(mut n: u32, l: u32) -> u32 { + fn bitreverse(mut n: usize, l: usize) -> usize { let mut r = 0; for _ in 0..l { r = (r << 1) | (n & 1); @@ -189,79 +178,98 @@ fn serial_fft(a: &mut [G], omega: G::Scalar, log_n: u32) { r } - let n = a.len() as u32; + let threads = multicore::current_num_threads(); + let log_threads = log2_floor(threads); + let n = a.len() as usize; assert_eq!(n, 1 << log_n); for k in 0..n { - let rk = bitreverse(k, log_n); + let rk = bitreverse(k, log_n as usize); if k < rk { - a.swap(rk as usize, k as usize); + a.swap(rk, k); } } - let mut m = 1; - for _ in 0..log_n { - let w_m = omega.pow_vartime(&[u64::from(n / (2 * m)), 0, 0, 0]); - - let mut k = 0; - while k < n { - let mut w = G::Scalar::one(); - for j in 0..m { - let mut t = a[(k + j + m) as usize]; - t.group_scale(&w); - a[(k + j + m) as usize] = a[(k + j) as usize]; - a[(k + j + m) as usize].group_sub(&t); - a[(k + j) as usize].group_add(&t); - w *= &w_m; - } + // precompute twiddle factors + let twiddles: Vec<_> = (0..(n / 2) as usize) + .scan(G::Scalar::one(), |w, _| { + let tw = *w; + w.group_scale(&omega); + Some(tw) + }) + .collect(); - k += 2 * m; + if log_n <= log_threads { + let mut chunk = 2_usize; + let mut twiddle_chunk = (n / 2) as usize; + for _ in 0..log_n { + a.chunks_mut(chunk).for_each(|coeffs| { + let (left, right) = coeffs.split_at_mut(chunk / 2); + + // case when twiddle factor is one + let (a, left) = left.split_at_mut(1); + let (b, right) = right.split_at_mut(1); + let t = b[0]; + b[0] = a[0]; + a[0].group_add(&t); + b[0].group_sub(&t); + + left.iter_mut() + .zip(right.iter_mut()) + .enumerate() + .for_each(|(i, (a, b))| { + let mut t = *b; + t.group_scale(&twiddles[(i + 1) * twiddle_chunk]); + *b = *a; + a.group_add(&t); + b.group_sub(&t); + }); + }); + chunk *= 2; + twiddle_chunk /= 2; } - - m *= 2; + } else { + recursive_butterfly_arithmetic(a, n, 1, &twiddles) } } -fn parallel_fft(a: &mut [G], omega: G::Scalar, log_n: u32, log_threads: u32) { - assert!(log_n >= log_threads); - - let num_threads = 1 << log_threads; - let log_new_n = log_n - log_threads; - let mut tmp = vec![vec![G::group_zero(); 1 << log_new_n]; num_threads]; - let new_omega = omega.pow_vartime(&[num_threads as u64, 0, 0, 0]); - - multicore::scope(|scope| { - let a = &*a; - - for (j, tmp) in tmp.iter_mut().enumerate() { - scope.spawn(move |_| { - // Shuffle into a sub-FFT - let omega_j = omega.pow_vartime(&[j as u64, 0, 0, 0]); - let omega_step = omega.pow_vartime(&[(j as u64) << log_new_n, 0, 0, 0]); - - let mut elt = G::Scalar::one(); - - for (i, tmp) in tmp.iter_mut().enumerate() { - for s in 0..num_threads { - let idx = (i + (s << log_new_n)) % (1 << log_n); - let mut t = a[idx]; - t.group_scale(&elt); - tmp.group_add(&t); - elt *= &omega_step; - } - elt *= &omega_j; - } - - // Perform sub-FFT - serial_fft(tmp, new_omega, log_new_n); +/// This perform recursive butterfly arithmetic +pub fn recursive_butterfly_arithmetic( + a: &mut [G], + n: usize, + twiddle_chunk: usize, + twiddles: &[G::Scalar], +) { + if n == 2 { + let t = a[1]; + a[1] = a[0]; + a[0].group_add(&t); + a[1].group_sub(&t); + } else { + let (left, right) = a.split_at_mut(n / 2); + rayon::join( + || recursive_butterfly_arithmetic(left, n / 2, twiddle_chunk * 2, twiddles), + || recursive_butterfly_arithmetic(right, n / 2, twiddle_chunk * 2, twiddles), + ); + + // case when twiddle factor is one + let (a, left) = left.split_at_mut(1); + let (b, right) = right.split_at_mut(1); + let t = b[0]; + b[0] = a[0]; + a[0].group_add(&t); + b[0].group_sub(&t); + + left.iter_mut() + .zip(right.iter_mut()) + .enumerate() + .for_each(|(i, (a, b))| { + let mut t = *b; + t.group_scale(&twiddles[(i + 1) * twiddle_chunk]); + *b = *a; + a.group_add(&t); + b.group_sub(&t); }); - } - }); - - // Unshuffle - let mask = (1 << log_threads) - 1; - for (idx, a) in a.iter_mut().enumerate() { - *a = tmp[idx & mask][idx >> log_threads]; } } diff --git a/halo2_proofs/src/circuit.rs b/halo2_proofs/src/circuit.rs index f2b1b13080..9451735d54 100644 --- a/halo2_proofs/src/circuit.rs +++ b/halo2_proofs/src/circuit.rs @@ -9,6 +9,9 @@ use crate::{ plonk::{Advice, Any, Assigned, Column, Error, Fixed, Instance, Selector, TableColumn}, }; +mod value; +pub use value::Value; + pub mod floor_planner; pub use floor_planner::single_pass::SimpleFloorPlanner; @@ -95,14 +98,14 @@ pub struct Cell { /// An assigned cell. #[derive(Clone, Debug)] pub struct AssignedCell { - value: Option, + value: Value, cell: Cell, _marker: PhantomData, } impl AssignedCell { /// Returns the value of the [`AssignedCell`]. - pub fn value(&self) -> Option<&V> { + pub fn value(&self) -> Value<&V> { self.value.as_ref() } @@ -117,8 +120,22 @@ where for<'v> Assigned: From<&'v V>, { /// Returns the field element value of the [`AssignedCell`]. - pub fn value_field(&self) -> Option> { - self.value().map(|v| v.into()) + pub fn value_field(&self) -> Value> { + self.value.to_field() + } +} + +impl AssignedCell, F> { + /// Evaluates this assigned cell's value directly, performing an unbatched inversion + /// if necessary. + /// + /// If the denominator is zero, the returned cell's value is zero. + pub fn evaluate(self) -> AssignedCell { + AssignedCell { + value: self.value.evaluate(), + cell: self.cell, + _marker: Default::default(), + } } } @@ -141,9 +158,8 @@ where A: Fn() -> AR, AR: Into, { - let assigned_cell = region.assign_advice(annotation, column, offset, || { - self.value.clone().ok_or(Error::Synthesis) - })?; + let assigned_cell = + region.assign_advice(annotation, column, offset, || self.value.clone())?; region.constrain_equal(assigned_cell.cell(), self.cell())?; Ok(assigned_cell) @@ -199,19 +215,19 @@ impl<'r, F: Field> Region<'r, F> { mut to: V, ) -> Result, Error> where - V: FnMut() -> Result + 'v, + V: FnMut() -> Value + 'v, for<'vr> Assigned: From<&'vr VR>, A: Fn() -> AR, AR: Into, { - let mut value = None; + let mut value = Value::unknown(); let cell = self.region .assign_advice(&|| annotation().into(), column, offset, &mut || { - let v = to()?; - let value_f = (&v).into(); - value = Some(v); - Ok(value_f) + let v = to(); + let value_f = v.to_field(); + value = v; + value_f })?; Ok(AssignedCell { @@ -247,7 +263,7 @@ impl<'r, F: Field> Region<'r, F> { )?; Ok(AssignedCell { - value: Some(constant), + value: Value::known(constant), cell, _marker: PhantomData, }) @@ -295,19 +311,19 @@ impl<'r, F: Field> Region<'r, F> { mut to: V, ) -> Result, Error> where - V: FnMut() -> Result + 'v, + V: FnMut() -> Value + 'v, for<'vr> Assigned: From<&'vr VR>, A: Fn() -> AR, AR: Into, { - let mut value = None; + let mut value = Value::unknown(); let cell = self.region .assign_fixed(&|| annotation().into(), column, offset, &mut || { - let v = to()?; - let value_f = (&v).into(); - value = Some(v); - Ok(value_f) + let v = to(); + let value_f = v.to_field(); + value = v; + value_f })?; Ok(AssignedCell { @@ -362,14 +378,14 @@ impl<'r, F: Field> Table<'r, F> { mut to: V, ) -> Result<(), Error> where - V: FnMut() -> Result + 'v, + V: FnMut() -> Value + 'v, VR: Into>, A: Fn() -> AR, AR: Into, { self.table .assign_cell(&|| annotation().into(), column, offset, &mut || { - to().map(|v| v.into()) + to().into_field() }) } } diff --git a/halo2_proofs/src/circuit/floor_planner/single_pass.rs b/halo2_proofs/src/circuit/floor_planner/single_pass.rs index f80d8591d9..c3774aa287 100644 --- a/halo2_proofs/src/circuit/floor_planner/single_pass.rs +++ b/halo2_proofs/src/circuit/floor_planner/single_pass.rs @@ -8,7 +8,7 @@ use ff::Field; use crate::{ circuit::{ layouter::{RegionColumn, RegionLayouter, RegionShape, TableLayouter}, - Cell, Layouter, Region, RegionIndex, RegionStart, Table, + Cell, Layouter, Region, RegionIndex, RegionStart, Table, Value, }, plonk::{ Advice, Any, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, Instance, @@ -131,7 +131,7 @@ impl<'a, F: Field, CS: Assignment + 'a> Layouter for SingleChipLayouter<'a || format!("Constant({:?})", constant.evaluate()), constants_column, *next_constant_row, - || Ok(constant), + || Value::known(constant), )?; self.cs.copy( constants_column.into(), @@ -280,7 +280,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Result, Error> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { self.layouter.cs.assign_advice( annotation, @@ -303,7 +303,8 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter offset: usize, constant: Assigned, ) -> Result { - let advice = self.assign_advice(annotation, column, offset, &mut || Ok(constant))?; + let advice = + self.assign_advice(annotation, column, offset, &mut || Value::known(constant))?; self.constrain_constant(advice, constant)?; Ok(advice) @@ -316,12 +317,10 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter row: usize, advice: Column, offset: usize, - ) -> Result<(Cell, Option), Error> { + ) -> Result<(Cell, Value), Error> { let value = self.layouter.cs.query_instance(instance, row)?; - let cell = self.assign_advice(annotation, advice, offset, &mut || { - value.ok_or(Error::Synthesis).map(|v| v.into()) - })?; + let cell = self.assign_advice(annotation, advice, offset, &mut || value.to_field())?; self.layouter.cs.copy( cell.column, @@ -338,7 +337,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Result, Error> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { self.layouter.cs.assign_fixed( annotation, @@ -376,9 +375,9 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter /// - The outer `Option` tracks whether the value in row 0 of the table column has been /// assigned yet. This will always be `Some` once a valid table has been completely /// assigned. -/// - The inner `Option` tracks whether the underlying `Assignment` is evaluating +/// - The inner `Value` tracks whether the underlying `Assignment` is evaluating /// witnesses or not. -type DefaultTableValue = Option>>; +type DefaultTableValue = Option>>; pub(crate) struct SimpleTableLayouter<'r, 'a, F: Field, CS: Assignment + 'a> { cs: &'a mut CS, @@ -414,7 +413,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> TableLayouter annotation: &'v (dyn Fn() -> String + 'v), column: TableColumn, offset: usize, - to: &'v mut (dyn FnMut() -> Result, Error> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result<(), Error> { if self.used_columns.contains(&column) { return Err(Error::Synthesis); // TODO better error @@ -422,14 +421,14 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> TableLayouter let entry = self.default_and_assigned.entry(column).or_default(); - let mut value = None; + let mut value = Value::unknown(); self.cs.assign_fixed( annotation, column.inner(), offset, // tables are always assigned starting at row 0 || { let res = to(); - value = res.as_ref().ok().cloned(); + value = res; res }, )?; diff --git a/halo2_proofs/src/circuit/floor_planner/v1.rs b/halo2_proofs/src/circuit/floor_planner/v1.rs index e9a0251dc6..bed390d27d 100644 --- a/halo2_proofs/src/circuit/floor_planner/v1.rs +++ b/halo2_proofs/src/circuit/floor_planner/v1.rs @@ -6,7 +6,7 @@ use crate::{ circuit::{ floor_planner::single_pass::SimpleTableLayouter, layouter::{RegionColumn, RegionLayouter, RegionShape, TableLayouter}, - Cell, Layouter, Region, RegionIndex, RegionStart, Table, + Cell, Layouter, Region, RegionIndex, RegionStart, Table, Value, }, plonk::{ Advice, Any, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, Instance, @@ -126,7 +126,7 @@ impl FloorPlanner for V1 { || format!("Constant({:?})", value.evaluate()), fixed_column, fixed_row, - || Ok(value), + || Value::known(value), )?; plan.cs.copy( fixed_column.into(), @@ -396,7 +396,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Result, Error> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { self.plan.cs.assign_advice( annotation, @@ -419,7 +419,8 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r offset: usize, constant: Assigned, ) -> Result { - let advice = self.assign_advice(annotation, column, offset, &mut || Ok(constant))?; + let advice = + self.assign_advice(annotation, column, offset, &mut || Value::known(constant))?; self.constrain_constant(advice, constant)?; Ok(advice) @@ -432,12 +433,10 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r row: usize, advice: Column, offset: usize, - ) -> Result<(Cell, Option), Error> { + ) -> Result<(Cell, Value), Error> { let value = self.plan.cs.query_instance(instance, row)?; - let cell = self.assign_advice(annotation, advice, offset, &mut || { - value.ok_or(Error::Synthesis).map(|v| v.into()) - })?; + let cell = self.assign_advice(annotation, advice, offset, &mut || value.to_field())?; self.plan.cs.copy( cell.column, @@ -454,7 +453,7 @@ impl<'r, 'a, F: Field, CS: Assignment + 'a> RegionLayouter for V1Region<'r annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Result, Error> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { self.plan.cs.assign_fixed( annotation, diff --git a/halo2_proofs/src/circuit/layouter.rs b/halo2_proofs/src/circuit/layouter.rs index 9f249d4c16..9436011fdc 100644 --- a/halo2_proofs/src/circuit/layouter.rs +++ b/halo2_proofs/src/circuit/layouter.rs @@ -6,7 +6,7 @@ use std::fmt; use ff::Field; -use super::{Cell, RegionIndex}; +use super::{Cell, RegionIndex, Value}; use crate::plonk::{Advice, Any, Assigned, Column, Error, Fixed, Instance, Selector, TableColumn}; /// Helper trait for implementing a custom [`Layouter`]. @@ -54,7 +54,7 @@ pub trait RegionLayouter: fmt::Debug { annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Result, Error> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result; /// Assigns a constant value to the column `advice` at `offset` within this region. @@ -82,7 +82,7 @@ pub trait RegionLayouter: fmt::Debug { row: usize, advice: Column, offset: usize, - ) -> Result<(Cell, Option), Error>; + ) -> Result<(Cell, Value), Error>; /// Assign a fixed value fn assign_fixed<'v>( @@ -90,7 +90,7 @@ pub trait RegionLayouter: fmt::Debug { annotation: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - to: &'v mut (dyn FnMut() -> Result, Error> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result; /// Constrains a cell to have a constant value. @@ -118,7 +118,7 @@ pub trait TableLayouter: fmt::Debug { annotation: &'v (dyn Fn() -> String + 'v), column: TableColumn, offset: usize, - to: &'v mut (dyn FnMut() -> Result, Error> + 'v), + to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result<(), Error>; } @@ -214,7 +214,7 @@ impl RegionLayouter for RegionShape { _: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - _to: &'v mut (dyn FnMut() -> Result, Error> + 'v), + _to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { self.columns.insert(Column::::from(column).into()); self.row_count = cmp::max(self.row_count, offset + 1); @@ -234,7 +234,7 @@ impl RegionLayouter for RegionShape { constant: Assigned, ) -> Result { // The rest is identical to witnessing an advice cell. - self.assign_advice(annotation, column, offset, &mut || Ok(constant)) + self.assign_advice(annotation, column, offset, &mut || Value::known(constant)) } fn assign_advice_from_instance<'v>( @@ -244,7 +244,7 @@ impl RegionLayouter for RegionShape { _: usize, advice: Column, offset: usize, - ) -> Result<(Cell, Option), Error> { + ) -> Result<(Cell, Value), Error> { self.columns.insert(Column::::from(advice).into()); self.row_count = cmp::max(self.row_count, offset + 1); @@ -254,7 +254,7 @@ impl RegionLayouter for RegionShape { row_offset: offset, column: advice.into(), }, - None, + Value::unknown(), )) } @@ -263,7 +263,7 @@ impl RegionLayouter for RegionShape { _: &'v (dyn Fn() -> String + 'v), column: Column, offset: usize, - _to: &'v mut (dyn FnMut() -> Result, Error> + 'v), + _to: &'v mut (dyn FnMut() -> Value> + 'v), ) -> Result { self.columns.insert(Column::::from(column).into()); self.row_count = cmp::max(self.row_count, offset + 1); diff --git a/halo2_proofs/src/circuit/value.rs b/halo2_proofs/src/circuit/value.rs new file mode 100644 index 0000000000..50b30c2676 --- /dev/null +++ b/halo2_proofs/src/circuit/value.rs @@ -0,0 +1,698 @@ +use std::borrow::Borrow; +use std::ops::{Add, Mul, Neg, Sub}; + +use group::ff::Field; + +use crate::plonk::{Assigned, Error}; + +/// A value that might exist within a circuit. +/// +/// This behaves like `Option` but differs in two key ways: +/// - It does not expose the enum cases, or provide an `Option::unwrap` equivalent. This +/// helps to ensure that unwitnessed values correctly propagate. +/// - It provides pass-through implementations of common traits such as `Add` and `Mul`, +/// for improved usability. +#[derive(Clone, Copy, Debug)] +pub struct Value { + inner: Option, +} + +impl Default for Value { + fn default() -> Self { + Self::unknown() + } +} + +impl Value { + /// Constructs an unwitnessed value. + pub const fn unknown() -> Self { + Self { inner: None } + } + + /// Constructs a known value. + /// + /// # Examples + /// + /// ``` + /// use halo2_proofs::circuit::Value; + /// + /// let v = Value::known(37); + /// ``` + pub const fn known(value: V) -> Self { + Self { inner: Some(value) } + } + + /// Obtains the inner value for assigning into the circuit. + /// + /// Returns `Error::Synthesis` if this is [`Value::unknown()`]. + pub(crate) fn assign(self) -> Result { + self.inner.ok_or(Error::Synthesis) + } + + /// Converts from `&Value` to `Value<&V>`. + pub fn as_ref(&self) -> Value<&V> { + Value { + inner: self.inner.as_ref(), + } + } + + /// Converts from `&mut Value` to `Value<&mut V>`. + pub fn as_mut(&mut self) -> Value<&mut V> { + Value { + inner: self.inner.as_mut(), + } + } + + /// Enforces an assertion on the contained value, if known. + /// + /// The assertion is ignored if `self` is [`Value::unknown()`]. Do not try to enforce + /// circuit constraints with this method! + /// + /// # Panics + /// + /// Panics if `f` returns `false`. + pub fn assert_if_known bool>(&self, f: F) { + if let Some(value) = self.inner.as_ref() { + assert!(f(value)); + } + } + + /// Checks the contained value for an error condition, if known. + /// + /// The error check is ignored if `self` is [`Value::unknown()`]. Do not try to + /// enforce circuit constraints with this method! + pub fn error_if_known_and bool>(&self, f: F) -> Result<(), Error> { + match self.inner.as_ref() { + Some(value) if f(value) => Err(Error::Synthesis), + _ => Ok(()), + } + } + + /// Maps a `Value` to `Value` by applying a function to the contained value. + pub fn map W>(self, f: F) -> Value { + Value { + inner: self.inner.map(f), + } + } + + /// Returns [`Value::unknown()`] if the value is [`Value::unknown()`], otherwise calls + /// `f` with the wrapped value and returns the result. + pub fn and_then Value>(self, f: F) -> Value { + match self.inner { + Some(v) => f(v), + None => Value::unknown(), + } + } + + /// Zips `self` with another `Value`. + /// + /// If `self` is `Value::known(s)` and `other` is `Value::known(o)`, this method + /// returns `Value::known((s, o))`. Otherwise, [`Value::unknown()`] is returned. + pub fn zip(self, other: Value) -> Value<(V, W)> { + Value { + inner: self.inner.zip(other.inner), + } + } +} + +impl Value<(V, W)> { + /// Unzips a value containing a tuple of two values. + /// + /// If `self` is `Value::known((a, b)), this method returns + /// `(Value::known(a), Value::known(b))`. Otherwise, + /// `(Value::unknown(), Value::unknown())` is returned. + pub fn unzip(self) -> (Value, Value) { + match self.inner { + Some((a, b)) => (Value::known(a), Value::known(b)), + None => (Value::unknown(), Value::unknown()), + } + } +} + +impl Value<&V> { + /// Maps a `Value<&V>` to a `Value` by copying the contents of the value. + #[must_use = "`self` will be dropped if the result is not used"] + pub fn copied(self) -> Value + where + V: Copy, + { + Value { + inner: self.inner.copied(), + } + } + + /// Maps a `Value<&V>` to a `Value` by cloning the contents of the value. + #[must_use = "`self` will be dropped if the result is not used"] + pub fn cloned(self) -> Value + where + V: Clone, + { + Value { + inner: self.inner.cloned(), + } + } +} + +impl Value<&mut V> { + /// Maps a `Value<&mut V>` to a `Value` by copying the contents of the value. + #[must_use = "`self` will be dropped if the result is not used"] + pub fn copied(self) -> Value + where + V: Copy, + { + Value { + inner: self.inner.copied(), + } + } + + /// Maps a `Value<&mut V>` to a `Value` by cloning the contents of the value. + #[must_use = "`self` will be dropped if the result is not used"] + pub fn cloned(self) -> Value + where + V: Clone, + { + Value { + inner: self.inner.cloned(), + } + } +} + +impl Value<[V; LEN]> { + /// Transposes a `Value<[V; LEN]>` into a `[Value; LEN]`. + /// + /// [`Value::unknown()`] will be mapped to `[Value::unknown(); LEN]`. + pub fn transpose_array(self) -> [Value; LEN] { + let mut ret = [Value::unknown(); LEN]; + if let Some(arr) = self.inner { + for (entry, value) in ret.iter_mut().zip(arr) { + *entry = Value::known(value); + } + } + ret + } +} + +impl Value +where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, +{ + /// Transposes a `Value>` into a `Vec>`. + /// + /// [`Value::unknown()`] will be mapped to `vec![Value::unknown(); length]`. + /// + /// # Panics + /// + /// Panics if `self` is `Value::known(values)` and `values.len() != length`. + pub fn transpose_vec(self, length: usize) -> Vec> { + match self.inner { + Some(values) => { + let values = values.into_iter(); + assert_eq!(values.len(), length); + values.map(Value::known).collect() + } + None => (0..length).map(|_| Value::unknown()).collect(), + } + } +} + +// +// FromIterator +// + +impl> FromIterator> for Value { + /// Takes each element in the [`Iterator`]: if it is [`Value::unknown()`], no further + /// elements are taken, and the [`Value::unknown()`] is returned. Should no + /// [`Value::unknown()`] occur, a container of type `V` containing the values of each + /// [`Value`] is returned. + fn from_iter>>(iter: I) -> Self { + Self { + inner: iter.into_iter().map(|v| v.inner).collect(), + } + } +} + +// +// Neg +// + +impl Neg for Value { + type Output = Value; + + fn neg(self) -> Self::Output { + Value { + inner: self.inner.map(|v| -v), + } + } +} + +// +// Add +// + +impl Add for Value +where + V: Add, +{ + type Output = Value; + + fn add(self, rhs: Self) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a + b), + } + } +} + +impl Add for &Value +where + for<'v> &'v V: Add, +{ + type Output = Value; + + fn add(self, rhs: Self) -> Self::Output { + Value { + inner: self + .inner + .as_ref() + .zip(rhs.inner.as_ref()) + .map(|(a, b)| a + b), + } + } +} + +impl Add> for Value +where + for<'v> V: Add<&'v V, Output = O>, +{ + type Output = Value; + + fn add(self, rhs: Value<&V>) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a + b), + } + } +} + +impl Add> for Value<&V> +where + for<'v> &'v V: Add, +{ + type Output = Value; + + fn add(self, rhs: Value) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a + b), + } + } +} + +impl Add<&Value> for Value +where + for<'v> V: Add<&'v V, Output = O>, +{ + type Output = Value; + + fn add(self, rhs: &Self) -> Self::Output { + self + rhs.as_ref() + } +} + +impl Add> for &Value +where + for<'v> &'v V: Add, +{ + type Output = Value; + + fn add(self, rhs: Value) -> Self::Output { + self.as_ref() + rhs + } +} + +// +// Sub +// + +impl Sub for Value +where + V: Sub, +{ + type Output = Value; + + fn sub(self, rhs: Self) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a - b), + } + } +} + +impl Sub for &Value +where + for<'v> &'v V: Sub, +{ + type Output = Value; + + fn sub(self, rhs: Self) -> Self::Output { + Value { + inner: self + .inner + .as_ref() + .zip(rhs.inner.as_ref()) + .map(|(a, b)| a - b), + } + } +} + +impl Sub> for Value +where + for<'v> V: Sub<&'v V, Output = O>, +{ + type Output = Value; + + fn sub(self, rhs: Value<&V>) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a - b), + } + } +} + +impl Sub> for Value<&V> +where + for<'v> &'v V: Sub, +{ + type Output = Value; + + fn sub(self, rhs: Value) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a - b), + } + } +} + +impl Sub<&Value> for Value +where + for<'v> V: Sub<&'v V, Output = O>, +{ + type Output = Value; + + fn sub(self, rhs: &Self) -> Self::Output { + self - rhs.as_ref() + } +} + +impl Sub> for &Value +where + for<'v> &'v V: Sub, +{ + type Output = Value; + + fn sub(self, rhs: Value) -> Self::Output { + self.as_ref() - rhs + } +} + +// +// Mul +// + +impl Mul for Value +where + V: Mul, +{ + type Output = Value; + + fn mul(self, rhs: Self) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a * b), + } + } +} + +impl Mul for &Value +where + for<'v> &'v V: Mul, +{ + type Output = Value; + + fn mul(self, rhs: Self) -> Self::Output { + Value { + inner: self + .inner + .as_ref() + .zip(rhs.inner.as_ref()) + .map(|(a, b)| a * b), + } + } +} + +impl Mul> for Value +where + for<'v> V: Mul<&'v V, Output = O>, +{ + type Output = Value; + + fn mul(self, rhs: Value<&V>) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a * b), + } + } +} + +impl Mul> for Value<&V> +where + for<'v> &'v V: Mul, +{ + type Output = Value; + + fn mul(self, rhs: Value) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a * b), + } + } +} + +impl Mul<&Value> for Value +where + for<'v> V: Mul<&'v V, Output = O>, +{ + type Output = Value; + + fn mul(self, rhs: &Self) -> Self::Output { + self * rhs.as_ref() + } +} + +impl Mul> for &Value +where + for<'v> &'v V: Mul, +{ + type Output = Value; + + fn mul(self, rhs: Value) -> Self::Output { + self.as_ref() * rhs + } +} + +// +// Assigned +// + +impl From> for Value> { + fn from(value: Value) -> Self { + Self { + inner: value.inner.map(Assigned::from), + } + } +} + +impl Add> for Value> { + type Output = Value>; + + fn add(self, rhs: Value) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a + b), + } + } +} + +impl Add for Value> { + type Output = Value>; + + fn add(self, rhs: F) -> Self::Output { + self + Value::known(rhs) + } +} + +impl Add> for Value<&Assigned> { + type Output = Value>; + + fn add(self, rhs: Value) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a + b), + } + } +} + +impl Add for Value<&Assigned> { + type Output = Value>; + + fn add(self, rhs: F) -> Self::Output { + self + Value::known(rhs) + } +} + +impl Sub> for Value> { + type Output = Value>; + + fn sub(self, rhs: Value) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a - b), + } + } +} + +impl Sub for Value> { + type Output = Value>; + + fn sub(self, rhs: F) -> Self::Output { + self - Value::known(rhs) + } +} + +impl Sub> for Value<&Assigned> { + type Output = Value>; + + fn sub(self, rhs: Value) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a - b), + } + } +} + +impl Sub for Value<&Assigned> { + type Output = Value>; + + fn sub(self, rhs: F) -> Self::Output { + self - Value::known(rhs) + } +} + +impl Mul> for Value> { + type Output = Value>; + + fn mul(self, rhs: Value) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a * b), + } + } +} + +impl Mul for Value> { + type Output = Value>; + + fn mul(self, rhs: F) -> Self::Output { + self * Value::known(rhs) + } +} + +impl Mul> for Value<&Assigned> { + type Output = Value>; + + fn mul(self, rhs: Value) -> Self::Output { + Value { + inner: self.inner.zip(rhs.inner).map(|(a, b)| a * b), + } + } +} + +impl Mul for Value<&Assigned> { + type Output = Value>; + + fn mul(self, rhs: F) -> Self::Output { + self * Value::known(rhs) + } +} + +impl Value { + /// Returns the field element corresponding to this value. + pub fn to_field(&self) -> Value> + where + for<'v> Assigned: From<&'v V>, + { + Value { + inner: self.inner.as_ref().map(|v| v.into()), + } + } + + /// Returns the field element corresponding to this value. + pub fn into_field(self) -> Value> + where + V: Into>, + { + Value { + inner: self.inner.map(|v| v.into()), + } + } + + /// Doubles this field element. + /// + /// # Examples + /// + /// If you have a `Value`, convert it to `Value>` first: + /// ``` + /// # use pasta_curves::pallas::Base as F; + /// use halo2_proofs::{circuit::Value, plonk::Assigned}; + /// + /// let v = Value::known(F::from(2)); + /// let v: Value> = v.into(); + /// v.double(); + /// ``` + pub fn double(&self) -> Value> + where + V: Borrow>, + { + Value { + inner: self.inner.as_ref().map(|v| v.borrow().double()), + } + } + + /// Squares this field element. + pub fn square(&self) -> Value> + where + V: Borrow>, + { + Value { + inner: self.inner.as_ref().map(|v| v.borrow().square()), + } + } + + /// Cubes this field element. + pub fn cube(&self) -> Value> + where + V: Borrow>, + { + Value { + inner: self.inner.as_ref().map(|v| v.borrow().cube()), + } + } + + /// Inverts this assigned value (taking the inverse of zero to be zero). + pub fn invert(&self) -> Value> + where + V: Borrow>, + { + Value { + inner: self.inner.as_ref().map(|v| v.borrow().invert()), + } + } +} + +impl Value> { + /// Evaluates this value directly, performing an unbatched inversion if necessary. + /// + /// If the denominator is zero, the returned value is zero. + pub fn evaluate(self) -> Value { + Value { + inner: self.inner.map(|v| v.evaluate()), + } + } +} diff --git a/halo2_proofs/src/dev.rs b/halo2_proofs/src/dev.rs index 8439310b7d..668dddbb39 100644 --- a/halo2_proofs/src/dev.rs +++ b/halo2_proofs/src/dev.rs @@ -13,6 +13,7 @@ use rayon::iter::ParallelIterator; use crate::plonk::Assigned; use crate::{ arithmetic::{FieldExt, Group}, + circuit, plonk::{ permutation, Advice, Any, Assignment, Circuit, Column, ColumnType, ConstraintSystem, Error, Expression, Fixed, FloorPlanner, Instance, Selector, VirtualCell, @@ -23,6 +24,9 @@ use crate::{ pub mod metadata; mod util; +mod failure; +pub use failure::{FailureLocation, VerifyFailure}; + pub mod cost; pub use cost::CircuitCost; @@ -36,210 +40,6 @@ mod graph; #[cfg_attr(docsrs, doc(cfg(feature = "dev-graph")))] pub use graph::{circuit_dot_graph, layout::CircuitLayout}; -/// The location within the circuit at which a particular [`VerifyFailure`] occurred. -#[derive(Debug, PartialEq)] -pub enum FailureLocation { - /// A location inside a region. - InRegion { - /// The region in which the failure occurred. - region: metadata::Region, - /// The offset (relative to the start of the region) at which the failure - /// occurred. - offset: usize, - }, - /// A location outside of a region. - OutsideRegion { - /// The circuit row on which the failure occurred. - row: usize, - }, -} - -impl fmt::Display for FailureLocation { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::InRegion { region, offset } => write!(f, "in {} at offset {}", region, offset), - Self::OutsideRegion { row } => { - write!(f, "on row {}", row) - } - } - } -} - -impl FailureLocation { - fn find_expressions<'a, F: Field>( - cs: &ConstraintSystem, - regions: &[Region], - failure_row: usize, - failure_expressions: impl Iterator>, - ) -> Self { - let failure_columns: HashSet> = failure_expressions - .flat_map(|expression| { - expression.evaluate( - &|_| vec![], - &|_| panic!("virtual selectors are removed during optimization"), - &|index, _, _| vec![cs.fixed_queries[index].0.into()], - &|index, _, _| vec![cs.advice_queries[index].0.into()], - &|index, _, _| vec![cs.instance_queries[index].0.into()], - &|a| a, - &|mut a, mut b| { - a.append(&mut b); - a - }, - &|mut a, mut b| { - a.append(&mut b); - a - }, - &|a, _| a, - ) - }) - .collect(); - - Self::find(regions, failure_row, failure_columns) - } - - /// Figures out whether the given row and columns overlap an assigned region. - fn find(regions: &[Region], failure_row: usize, failure_columns: HashSet>) -> Self { - regions - .iter() - .enumerate() - .find(|(_, r)| { - let (start, end) = r.rows.unwrap(); - // We match the region if any input columns overlap, rather than all of - // them, because matching complex selector columns is hard. As long as - // regions are rectangles, and failures occur due to assignments entirely - // within single regions, "any" will be equivalent to "all". If these - // assumptions change, we'll start getting bug reports from users :) - (start..=end).contains(&failure_row) && !failure_columns.is_disjoint(&r.columns) - }) - .map(|(r_i, r)| FailureLocation::InRegion { - region: (r_i, r.name.clone()).into(), - offset: failure_row as usize - r.rows.unwrap().0 as usize, - }) - .unwrap_or_else(|| FailureLocation::OutsideRegion { - row: failure_row as usize, - }) - } -} - -/// The reasons why a particular circuit is not satisfied. -#[derive(Debug, PartialEq)] -pub enum VerifyFailure { - /// A cell used in an active gate was not assigned to. - CellNotAssigned { - /// The index of the active gate. - gate: metadata::Gate, - /// The region in which this cell should be assigned. - region: metadata::Region, - /// The column in which this cell should be assigned. - column: Column, - /// The offset (relative to the start of the region) at which this cell should be - /// assigned. This may be negative (for example, if a selector enables a gate at - /// offset 0, but the gate uses `Rotation::prev()`). - offset: isize, - }, - /// A constraint was not satisfied for a particular row. - ConstraintNotSatisfied { - /// The polynomial constraint that is not satisfied. - constraint: metadata::Constraint, - /// The location at which this constraint is not satisfied. - /// - /// `FailureLocation::OutsideRegion` is usually caused by a constraint that does - /// not contain a selector, and as a result is active on every row. - location: FailureLocation, - /// The values of the virtual cells used by this constraint. - cell_values: Vec<(metadata::VirtualCell, String)>, - }, - /// A constraint was active on an unusable row, and is likely missing a selector. - ConstraintPoisoned { - /// The polynomial constraint that is not satisfied. - constraint: metadata::Constraint, - }, - /// A lookup input did not exist in its corresponding table. - Lookup { - /// The name of the lookup that is not satisfied. - name: &'static str, - /// The index of the lookup that is not satisfied. These indices are assigned in - /// the order in which `ConstraintSystem::lookup` is called during - /// `Circuit::configure`. - lookup_index: usize, - /// The location at which the lookup is not satisfied. - /// - /// `FailureLocation::InRegion` is most common, and may be due to the intentional - /// use of a lookup (if its inputs are conditional on a complex selector), or an - /// unintentional lookup constraint that overlaps the region (indicating that the - /// lookup's inputs should be made conditional). - /// - /// `FailureLocation::OutsideRegion` is uncommon, and could mean that: - /// - The input expressions do not correctly constrain a default value that exists - /// in the table when the lookup is not being used. - /// - The input expressions use a column queried at a non-zero `Rotation`, and the - /// lookup is active on a row adjacent to an unrelated region. - location: FailureLocation, - }, - /// A permutation did not preserve the original value of a cell. - Permutation { - /// The column in which this permutation is not satisfied. - column: metadata::Column, - /// The row on which this permutation is not satisfied. - row: usize, - }, -} - -impl fmt::Display for VerifyFailure { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::CellNotAssigned { - gate, - region, - column, - offset, - } => { - write!( - f, - "{} uses {}, which requires cell in column {:?} at offset {} to be assigned.", - region, gate, column, offset - ) - } - Self::ConstraintNotSatisfied { - constraint, - location, - cell_values, - } => { - writeln!(f, "{} is not satisfied {}", constraint, location)?; - for (name, value) in cell_values { - writeln!(f, "- {} = {}", name, value)?; - } - Ok(()) - } - Self::ConstraintPoisoned { constraint } => { - write!( - f, - "{} is active on an unusable row - missing selector?", - constraint - ) - } - Self::Lookup { - name, - lookup_index, - location, - } => { - write!( - f, - "Lookup {}(index: {}) is not satisfied {}", - name, lookup_index, location - ) - } - Self::Permutation { column, row } => { - write!( - f, - "Equality constraint not satisfied by cell ({:?}, {})", - column, row - ) - } - } - } -} - #[derive(Debug)] struct Region { /// The name of the region. Not required to be unique. @@ -380,7 +180,7 @@ impl Mul for Value { /// ``` /// use halo2_proofs::{ /// arithmetic::FieldExt, -/// circuit::{Layouter, SimpleFloorPlanner}, +/// circuit::{Layouter, SimpleFloorPlanner, Value}, /// dev::{FailureLocation, MockProver, VerifyFailure}, /// pairing::bn256::Fr as Fp, /// plonk::{Advice, Any, Circuit, Column, ConstraintSystem, Error, Selector}, @@ -398,8 +198,8 @@ impl Mul for Value { /// /// #[derive(Clone, Default)] /// struct MyCircuit { -/// a: Option, -/// b: Option, +/// a: Value, +/// b: Value, /// } /// /// impl Circuit for MyCircuit { @@ -433,15 +233,13 @@ impl Mul for Value { /// layouter.assign_region(|| "Example region", |mut region| { /// config.s.enable(&mut region, 0)?; /// region.assign_advice(|| "a", config.a, 0, || { -/// self.a.map(|v| F::from(v)).ok_or(Error::Synthesis) +/// self.a.map(F::from) /// })?; /// region.assign_advice(|| "b", config.b, 0, || { -/// self.b.map(|v| F::from(v)).ok_or(Error::Synthesis) +/// self.b.map(F::from) /// })?; /// region.assign_advice(|| "c", config.c, 0, || { -/// self.a -/// .and_then(|a| self.b.map(|b| F::from(a * b))) -/// .ok_or(Error::Synthesis) +/// (self.a * self.b).map(F::from) /// })?; /// Ok(()) /// }) @@ -450,8 +248,8 @@ impl Mul for Value { /// /// // Assemble the private inputs to the circuit. /// let circuit = MyCircuit { -/// a: Some(2), -/// b: Some(4), +/// a: Value::known(2), +/// b: Value::known(4), /// }; /// /// // This circuit has no public inputs. @@ -553,7 +351,11 @@ impl Assignment for MockProver { Ok(()) } - fn query_instance(&self, column: Column, row: usize) -> Result, Error> { + fn query_instance( + &self, + column: Column, + row: usize, + ) -> Result, Error> { if !self.usable_rows.contains(&row) { return Err(Error::not_enough_rows_available(self.k)); } @@ -561,7 +363,7 @@ impl Assignment for MockProver { self.instance .get(column.index()) .and_then(|column| column.get(row)) - .map(|v| Some(*v)) + .map(|v| circuit::Value::known(*v)) .ok_or(Error::BoundsFailure) } @@ -573,7 +375,7 @@ impl Assignment for MockProver { to: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> circuit::Value, VR: Into>, A: FnOnce() -> AR, AR: Into, @@ -591,7 +393,8 @@ impl Assignment for MockProver { .advice .get_mut(column.index()) .and_then(|v| v.get_mut(row)) - .ok_or(Error::BoundsFailure)? = CellValue::Assigned(to()?.into().evaluate()); + .ok_or(Error::BoundsFailure)? = + CellValue::Assigned(to().into_field().evaluate().assign()?); Ok(()) } @@ -604,7 +407,7 @@ impl Assignment for MockProver { to: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> circuit::Value, VR: Into>, A: FnOnce() -> AR, AR: Into, @@ -622,7 +425,8 @@ impl Assignment for MockProver { .fixed .get_mut(column.index()) .and_then(|v| v.get_mut(row)) - .ok_or(Error::BoundsFailure)? = CellValue::Assigned(to()?.into().evaluate()); + .ok_or(Error::BoundsFailure)? = + CellValue::Assigned(to().into_field().evaluate().assign()?); Ok(()) } @@ -646,14 +450,14 @@ impl Assignment for MockProver { &mut self, col: Column, from_row: usize, - to: Option>, + to: circuit::Value>, ) -> Result<(), Error> { if !self.usable_rows.contains(&from_row) { return Err(Error::not_enough_rows_available(self.k)); } for row in self.usable_rows.clone().skip(from_row) { - self.assign_fixed(|| "", col, row, || to.ok_or(Error::Synthesis))?; + self.assign_fixed(|| "", col, row, || to)?; } Ok(()) @@ -944,9 +748,14 @@ impl MockProver { match poly.evaluate_lazy( &|scalar| Value::Real(scalar), &|_| panic!("virtual selectors are removed during optimization"), - &load(n, row, &self.cs.fixed_queries, &self.fixed), - &load(n, row, &self.cs.advice_queries, &self.advice), - &load_instance(n, row, &self.cs.instance_queries, &self.instance), + &util::load(n, row, &self.cs.fixed_queries, &self.fixed), + &util::load(n, row, &self.cs.advice_queries, &self.advice), + &util::load_instance( + n, + row, + &self.cs.instance_queries, + &self.instance, + ), &|a| -a, &|a, b| a + b, &|a, b| a * b, @@ -970,9 +779,9 @@ impl MockProver { cell_values: util::cell_values( gate, poly, - &load(n, row, &self.cs.fixed_queries, &self.fixed), - &load(n, row, &self.cs.advice_queries, &self.advice), - &load_instance( + &util::load(n, row, &self.cs.fixed_queries, &self.fixed), + &util::load(n, row, &self.cs.advice_queries, &self.advice), + &util::load_instance( n, row, &self.cs.instance_queries, @@ -1006,23 +815,23 @@ impl MockProver { expression.evaluate_lazy( &|scalar| Value::Real(scalar), &|_| panic!("virtual selectors are removed during optimization"), - &|index, _, _| { - let query = self.cs.fixed_queries[index]; + &|query| { + let query = self.cs.fixed_queries[query.index]; let column_index = query.0.index(); let rotation = query.1 .0; self.fixed[column_index][(row + n + rotation) as usize % n as usize] .into() }, - &|index, _, _| { - let query = self.cs.advice_queries[index]; + &|query| { + let query = self.cs.advice_queries[query.index]; let column_index = query.0.index(); let rotation = query.1 .0; self.advice[column_index] [(row + n + rotation) as usize % n as usize] .into() }, - &|index, _, _| { - let query = self.cs.instance_queries[index]; + &|query| { + let query = self.cs.instance_queries[query.index]; let column_index = query.0.index(); let rotation = query.1 .0; Value::Real( @@ -1038,6 +847,21 @@ impl MockProver { ) }; + assert!(lookup.table_expressions.len() == lookup.input_expressions.len()); + assert!(self.usable_rows.end > 0); + + // We optimize on the basis that the table might have been filled so that the last + // usable row now has the fill contents (it doesn't matter if there was no filling). + // Note that this "fill row" necessarily exists in the table, and we use that fact to + // slightly simplify the optimization: we're only trying to check that all input rows + // are contained in the table, and so we can safely just drop input rows that + // match the fill row. + let fill_row: Vec<_> = lookup + .table_expressions + .iter() + .map(move |c| load(c, self.usable_rows.end - 1)) + .collect(); + // In the real prover, the lookup expressions are never enforced on // unusable rows, due to the (1 - (l_last(X) + l_blind(X))) term. let usable_row_vec: Vec<_> = self.usable_rows.clone().into_iter().collect(); @@ -1147,6 +971,25 @@ impl MockProver { Err(errors) } } + + /// Panics if the circuit being checked by this `MockProver` is not satisfied. + /// + /// Any verification failures will be pretty-printed to stderr before the function + /// panics. + /// + /// Apart from the stderr output, this method is equivalent to: + /// ```ignore + /// assert_eq!(prover.verify(), Ok(())); + /// ``` + pub fn assert_satisfied(&self) { + if let Err(errs) = self.verify() { + for err in errs { + err.emit(self); + eprintln!(); + } + panic!("circuit was not satisfied"); + } + } } #[cfg(test)] @@ -1155,7 +998,7 @@ mod tests { use super::{FailureLocation, MockProver, VerifyFailure}; use crate::{ - circuit::{Layouter, SimpleFloorPlanner}, + circuit::{Layouter, SimpleFloorPlanner, Value}, plonk::{ Advice, Any, Circuit, Column, ConstraintSystem, Error, Expression, Selector, TableColumn, @@ -1212,7 +1055,7 @@ mod tests { config.q.enable(&mut region, 1)?; // Assign a = 0. - region.assign_advice(|| "a", config.a, 0, || Ok(Fp::zero()))?; + region.assign_advice(|| "a", config.a, 0, || Value::known(Fp::zero()))?; // BUG: Forget to assign b = 0! This could go unnoticed during // development, because cell values default to zero, which in this @@ -1229,6 +1072,7 @@ mod tests { Err(vec![VerifyFailure::CellNotAssigned { gate: (0, "Equality check").into(), region: (0, "Faulty synthesis".to_owned()).into(), + gate_offset: 1, column: Column::new(1, Any::Advice), offset: 1, }]) @@ -1289,7 +1133,7 @@ mod tests { || format!("table[{}] = {}", i, 2 * i), config.table, i - 1, - || Ok(Fp::from(2 * i as u64)), + || Value::known(Fp::from(2 * i as u64)), ) }) .fold(Ok(()), |acc, res| acc.and(res)) @@ -1304,8 +1148,18 @@ mod tests { config.q.enable(&mut region, 1)?; // Assign a = 2 and a = 6. - region.assign_advice(|| "a = 2", config.a, 0, || Ok(Fp::from(2)))?; - region.assign_advice(|| "a = 6", config.a, 1, || Ok(Fp::from(6)))?; + region.assign_advice( + || "a = 2", + config.a, + 0, + || Value::known(Fp::from(2)), + )?; + region.assign_advice( + || "a = 6", + config.a, + 1, + || Value::known(Fp::from(6)), + )?; Ok(()) }, @@ -1319,10 +1173,20 @@ mod tests { config.q.enable(&mut region, 1)?; // Assign a = 4. - region.assign_advice(|| "a = 4", config.a, 0, || Ok(Fp::from(4)))?; + region.assign_advice( + || "a = 4", + config.a, + 0, + || Value::known(Fp::from(4)), + )?; // BUG: Assign a = 5, which doesn't exist in the table! - region.assign_advice(|| "a = 5", config.a, 1, || Ok(Fp::from(5)))?; + region.assign_advice( + || "a = 5", + config.a, + 1, + || Value::known(Fp::from(5)), + )?; Ok(()) }, diff --git a/halo2_proofs/src/dev/cost.rs b/halo2_proofs/src/dev/cost.rs index 4a1cde0a69..35208a055e 100644 --- a/halo2_proofs/src/dev/cost.rs +++ b/halo2_proofs/src/dev/cost.rs @@ -11,6 +11,7 @@ use ff::{Field, PrimeField}; use group::prime::PrimeGroup; use crate::{ + circuit::Value, plonk::{ Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed, FloorPlanner, Instance, Selector, @@ -68,8 +69,8 @@ impl Assignment for Assembly { Ok(()) } - fn query_instance(&self, _: Column, _: usize) -> Result, Error> { - Ok(None) + fn query_instance(&self, _: Column, _: usize) -> Result, Error> { + Ok(Value::unknown()) } fn assign_advice( @@ -80,7 +81,7 @@ impl Assignment for Assembly { _: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> Value, VR: Into>, A: FnOnce() -> AR, AR: Into, @@ -96,7 +97,7 @@ impl Assignment for Assembly { _: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> Value, VR: Into>, A: FnOnce() -> AR, AR: Into, @@ -112,7 +113,7 @@ impl Assignment for Assembly { &mut self, _: Column, _: usize, - _: Option>, + _: Value>, ) -> Result<(), Error> { Ok(()) } diff --git a/halo2_proofs/src/dev/failure.rs b/halo2_proofs/src/dev/failure.rs new file mode 100644 index 0000000000..e4d53ca723 --- /dev/null +++ b/halo2_proofs/src/dev/failure.rs @@ -0,0 +1,539 @@ +use std::collections::{BTreeMap, BTreeSet, HashSet}; +use std::fmt; +use std::iter; + +use group::ff::Field; +use pasta_curves::arithmetic::FieldExt; + +use super::{ + metadata, + util::{self, AnyQuery}, + MockProver, Region, +}; +use crate::{ + dev::Value, + plonk::{Any, Column, ConstraintSystem, Expression, Gate}, + poly::Rotation, +}; + +mod emitter; + +/// The location within the circuit at which a particular [`VerifyFailure`] occurred. +#[derive(Debug, PartialEq)] +pub enum FailureLocation { + /// A location inside a region. + InRegion { + /// The region in which the failure occurred. + region: metadata::Region, + /// The offset (relative to the start of the region) at which the failure + /// occurred. + offset: usize, + }, + /// A location outside of a region. + OutsideRegion { + /// The circuit row on which the failure occurred. + row: usize, + }, +} + +impl fmt::Display for FailureLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InRegion { region, offset } => write!(f, "in {} at offset {}", region, offset), + Self::OutsideRegion { row } => { + write!(f, "outside any region, on row {}", row) + } + } + } +} + +impl FailureLocation { + pub(super) fn find_expressions<'a, F: Field>( + cs: &ConstraintSystem, + regions: &[Region], + failure_row: usize, + failure_expressions: impl Iterator>, + ) -> Self { + let failure_columns: HashSet> = failure_expressions + .flat_map(|expression| { + expression.evaluate( + &|_| vec![], + &|_| panic!("virtual selectors are removed during optimization"), + &|query| vec![cs.fixed_queries[query.index].0.into()], + &|query| vec![cs.advice_queries[query.index].0.into()], + &|query| vec![cs.instance_queries[query.index].0.into()], + &|a| a, + &|mut a, mut b| { + a.append(&mut b); + a + }, + &|mut a, mut b| { + a.append(&mut b); + a + }, + &|a, _| a, + ) + }) + .collect(); + + Self::find(regions, failure_row, failure_columns) + } + + /// Figures out whether the given row and columns overlap an assigned region. + pub(super) fn find( + regions: &[Region], + failure_row: usize, + failure_columns: HashSet>, + ) -> Self { + regions + .iter() + .enumerate() + .find(|(_, r)| { + let (start, end) = r.rows.unwrap(); + // We match the region if any input columns overlap, rather than all of + // them, because matching complex selector columns is hard. As long as + // regions are rectangles, and failures occur due to assignments entirely + // within single regions, "any" will be equivalent to "all". If these + // assumptions change, we'll start getting bug reports from users :) + (start..=end).contains(&failure_row) && !failure_columns.is_disjoint(&r.columns) + }) + .map(|(r_i, r)| FailureLocation::InRegion { + region: (r_i, r.name.clone()).into(), + offset: failure_row as usize - r.rows.unwrap().0 as usize, + }) + .unwrap_or_else(|| FailureLocation::OutsideRegion { + row: failure_row as usize, + }) + } +} + +/// The reasons why a particular circuit is not satisfied. +#[derive(Debug, PartialEq)] +pub enum VerifyFailure { + /// A cell used in an active gate was not assigned to. + CellNotAssigned { + /// The index of the active gate. + gate: metadata::Gate, + /// The region in which this cell should be assigned. + region: metadata::Region, + /// The offset (relative to the start of the region) at which the active gate + /// queries this cell. + gate_offset: usize, + /// The column in which this cell should be assigned. + column: Column, + /// The offset (relative to the start of the region) at which this cell should be + /// assigned. This may be negative (for example, if a selector enables a gate at + /// offset 0, but the gate uses `Rotation::prev()`). + offset: isize, + }, + /// A constraint was not satisfied for a particular row. + ConstraintNotSatisfied { + /// The polynomial constraint that is not satisfied. + constraint: metadata::Constraint, + /// The location at which this constraint is not satisfied. + /// + /// `FailureLocation::OutsideRegion` is usually caused by a constraint that does + /// not contain a selector, and as a result is active on every row. + location: FailureLocation, + /// The values of the virtual cells used by this constraint. + cell_values: Vec<(metadata::VirtualCell, String)>, + }, + /// A constraint was active on an unusable row, and is likely missing a selector. + ConstraintPoisoned { + /// The polynomial constraint that is not satisfied. + constraint: metadata::Constraint, + }, + /// A lookup input did not exist in its corresponding table. + Lookup { + /// The index of the lookup that is not satisfied. These indices are assigned in + /// the order in which `ConstraintSystem::lookup` is called during + /// `Circuit::configure`. + lookup_index: usize, + /// The location at which the lookup is not satisfied. + /// + /// `FailureLocation::InRegion` is most common, and may be due to the intentional + /// use of a lookup (if its inputs are conditional on a complex selector), or an + /// unintentional lookup constraint that overlaps the region (indicating that the + /// lookup's inputs should be made conditional). + /// + /// `FailureLocation::OutsideRegion` is uncommon, and could mean that: + /// - The input expressions do not correctly constrain a default value that exists + /// in the table when the lookup is not being used. + /// - The input expressions use a column queried at a non-zero `Rotation`, and the + /// lookup is active on a row adjacent to an unrelated region. + location: FailureLocation, + }, + /// A permutation did not preserve the original value of a cell. + Permutation { + /// The column in which this permutation is not satisfied. + column: metadata::Column, + /// The location at which the permutation is not satisfied. + location: FailureLocation, + }, +} + +impl fmt::Display for VerifyFailure { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::CellNotAssigned { + gate, + region, + gate_offset, + column, + offset, + } => { + write!( + f, + "{} uses {} at offset {}, which requires cell in column {:?} at offset {} to be assigned.", + region, gate, gate_offset, column, offset + ) + } + Self::ConstraintNotSatisfied { + constraint, + location, + cell_values, + } => { + writeln!(f, "{} is not satisfied {}", constraint, location)?; + for (name, value) in cell_values { + writeln!(f, "- {} = {}", name, value)?; + } + Ok(()) + } + Self::ConstraintPoisoned { constraint } => { + write!( + f, + "{} is active on an unusable row - missing selector?", + constraint + ) + } + Self::Lookup { + lookup_index, + location, + } => write!(f, "Lookup {} is not satisfied {}", lookup_index, location), + Self::Permutation { column, location } => { + write!( + f, + "Equality constraint not satisfied by cell ({:?}, {})", + column, location + ) + } + } + } +} + +/// Renders `VerifyFailure::CellNotAssigned`. +/// +/// ```text +/// error: cell not assigned +/// Cell layout in region 'Faulty synthesis': +/// | Offset | A0 | A1 | +/// +--------+----+----+ +/// | 0 | x0 | | +/// | 1 | | X | <--{ X marks the spot! 🦜 +/// +/// Gate 'Equality check' (applied at offset 1) queries these cells. +/// ``` +fn render_cell_not_assigned( + gates: &[Gate], + gate: &metadata::Gate, + region: &metadata::Region, + gate_offset: usize, + column: Column, + offset: isize, +) { + // Collect the necessary rendering information: + // - The columns involved in this gate. + // - How many cells are in each column. + // - The grid of cell values, indexed by rotation. + let mut columns = BTreeMap::::default(); + let mut layout = BTreeMap::>::default(); + for (i, cell) in gates[gate.index].queried_cells().iter().enumerate() { + let cell_column = cell.column.into(); + *columns.entry(cell_column).or_default() += 1; + layout + .entry(cell.rotation.0) + .or_default() + .entry(cell_column) + .or_insert_with(|| { + if cell.column == column && gate_offset as i32 + cell.rotation.0 == offset as i32 { + "X".to_string() + } else { + format!("x{}", i) + } + }); + } + + eprintln!("error: cell not assigned"); + emitter::render_cell_layout( + " ", + &FailureLocation::InRegion { + region: region.clone(), + offset: gate_offset, + }, + &columns, + &layout, + |row_offset, rotation| { + if (row_offset.unwrap() + rotation) as isize == offset { + eprint!(" <--{{ X marks the spot! 🦜"); + } + }, + ); + eprintln!(); + eprintln!( + " Gate '{}' (applied at offset {}) queries these cells.", + gate.name, gate_offset + ); +} + +/// Renders `VerifyFailure::ConstraintNotSatisfied`. +/// +/// ```text +/// error: constraint not satisfied +/// Cell layout in region 'somewhere': +/// | Offset | A0 | +/// +--------+----+ +/// | 0 | x0 | <--{ Gate 'foo' applied here +/// | 1 | x1 | +/// +/// Constraint 'bar': +/// x1 + x1 * 0x100 + x1 * 0x10000 + x1 * 0x100_0000 - x0 = 0 +/// +/// Assigned cell values: +/// x0 = 0x5 +/// x1 = 0x5 +/// ``` +fn render_constraint_not_satisfied( + gates: &[Gate], + constraint: &metadata::Constraint, + location: &FailureLocation, + cell_values: &[(metadata::VirtualCell, String)], +) { + // Collect the necessary rendering information: + // - The columns involved in this constraint. + // - How many cells are in each column. + // - The grid of cell values, indexed by rotation. + let mut columns = BTreeMap::::default(); + let mut layout = BTreeMap::>::default(); + for (i, (cell, _)) in cell_values.iter().enumerate() { + *columns.entry(cell.column).or_default() += 1; + layout + .entry(cell.rotation) + .or_default() + .entry(cell.column) + .or_insert(format!("x{}", i)); + } + + eprintln!("error: constraint not satisfied"); + emitter::render_cell_layout(" ", location, &columns, &layout, |_, rotation| { + if rotation == 0 { + eprint!(" <--{{ Gate '{}' applied here", constraint.gate.name); + } + }); + + // Print the unsatisfied constraint, in terms of the local variables. + eprintln!(); + eprintln!(" Constraint '{}':", constraint.name); + eprintln!( + " {} = 0", + emitter::expression_to_string( + &gates[constraint.gate.index].polynomials()[constraint.index], + &layout + ) + ); + + // Print the map from local variables to assigned values. + eprintln!(); + eprintln!(" Assigned cell values:"); + for (i, (_, value)) in cell_values.iter().enumerate() { + eprintln!(" x{} = {}", i, value); + } +} + +/// Renders `VerifyFailure::Lookup`. +/// +/// ```text +/// error: lookup input does not exist in table +/// (L0) ∉ (F0) +/// +/// Lookup inputs: +/// L0 = x1 * x0 + (1 - x1) * 0x2 +/// ^ +/// | Cell layout in region 'Faulty synthesis': +/// | | Offset | A0 | F1 | +/// | +--------+----+----+ +/// | | 1 | x0 | x1 | <--{ Lookup inputs queried here +/// | +/// | Assigned cell values: +/// | x0 = 0x5 +/// | x1 = 1 +/// ``` +fn render_lookup( + prover: &MockProver, + lookup_index: usize, + location: &FailureLocation, +) { + let n = prover.n as i32; + let cs = &prover.cs; + let lookup = &cs.lookups[lookup_index]; + + // Get the absolute row on which the lookup's inputs are being queried, so we can + // fetch the input values. + let row = match location { + FailureLocation::InRegion { region, offset } => { + prover.regions[region.index].rows.unwrap().0 + offset + } + FailureLocation::OutsideRegion { row } => *row, + } as i32; + + // Recover the fixed columns from the table expressions. We don't allow composite + // expressions for the table side of lookups. + let table_columns = lookup.table_expressions.iter().map(|expr| { + expr.evaluate( + &|_| panic!("no constants in table expressions"), + &|_| panic!("no selectors in table expressions"), + &|query| format!("F{}", query.column_index), + &|_| panic!("no advice columns in table expressions"), + &|_| panic!("no instance columns in table expressions"), + &|_| panic!("no negations in table expressions"), + &|_, _| panic!("no sums in table expressions"), + &|_, _| panic!("no products in table expressions"), + &|_, _| panic!("no scaling in table expressions"), + ) + }); + + fn cell_value<'a, F: FieldExt, Q: Into + Copy>( + column_type: Any, + load: impl Fn(Q) -> Value + 'a, + ) -> impl Fn(Q) -> BTreeMap + 'a { + move |query| { + let AnyQuery { + column_index, + rotation, + .. + } = query.into(); + Some(( + ((column_type, column_index).into(), rotation.0).into(), + match load(query) { + Value::Real(v) => util::format_value(v), + Value::Poison => unreachable!(), + }, + )) + .into_iter() + .collect() + } + } + + eprintln!("error: lookup input does not exist in table"); + eprint!(" ("); + for i in 0..lookup.input_expressions.len() { + eprint!("{}L{}", if i == 0 { "" } else { ", " }, i); + } + eprint!(") ∉ ("); + for (i, column) in table_columns.enumerate() { + eprint!("{}{}", if i == 0 { "" } else { ", " }, column); + } + eprintln!(")"); + + eprintln!(); + eprintln!(" Lookup inputs:"); + for (i, input) in lookup.input_expressions.iter().enumerate() { + // Fetch the cell values (since we don't store them in VerifyFailure::Lookup). + let cell_values = input.evaluate( + &|_| BTreeMap::default(), + &|_| panic!("virtual selectors are removed during optimization"), + &cell_value( + Any::Fixed, + &util::load(n, row, &cs.fixed_queries, &prover.fixed), + ), + &cell_value( + Any::Advice, + &util::load(n, row, &cs.advice_queries, &prover.advice), + ), + &cell_value( + Any::Instance, + &util::load_instance(n, row, &cs.instance_queries, &prover.instance), + ), + &|a| a, + &|mut a, mut b| { + a.append(&mut b); + a + }, + &|mut a, mut b| { + a.append(&mut b); + a + }, + &|a, _| a, + ); + + // Collect the necessary rendering information: + // - The columns involved in this constraint. + // - How many cells are in each column. + // - The grid of cell values, indexed by rotation. + let mut columns = BTreeMap::::default(); + let mut layout = BTreeMap::>::default(); + for (i, (cell, _)) in cell_values.iter().enumerate() { + *columns.entry(cell.column).or_default() += 1; + layout + .entry(cell.rotation) + .or_default() + .entry(cell.column) + .or_insert(format!("x{}", i)); + } + + if i != 0 { + eprintln!(); + } + eprintln!( + " L{} = {}", + i, + emitter::expression_to_string(input, &layout) + ); + eprintln!(" ^"); + emitter::render_cell_layout(" | ", location, &columns, &layout, |_, rotation| { + if rotation == 0 { + eprint!(" <--{{ Lookup inputs queried here"); + } + }); + + // Print the map from local variables to assigned values. + eprintln!(" |"); + eprintln!(" | Assigned cell values:"); + for (i, (_, value)) in cell_values.iter().enumerate() { + eprintln!(" | x{} = {}", i, value); + } + } +} + +impl VerifyFailure { + /// Emits this failure in pretty-printed format to stderr. + pub(super) fn emit(&self, prover: &MockProver) { + match self { + Self::CellNotAssigned { + gate, + region, + gate_offset, + column, + offset, + } => render_cell_not_assigned( + &prover.cs.gates, + gate, + region, + *gate_offset, + *column, + *offset, + ), + Self::ConstraintNotSatisfied { + constraint, + location, + cell_values, + } => { + render_constraint_not_satisfied(&prover.cs.gates, constraint, location, cell_values) + } + Self::Lookup { + lookup_index, + location, + } => render_lookup(prover, *lookup_index, location), + _ => eprintln!("{}", self), + } + } +} diff --git a/halo2_proofs/src/dev/failure/emitter.rs b/halo2_proofs/src/dev/failure/emitter.rs new file mode 100644 index 0000000000..071dca6f16 --- /dev/null +++ b/halo2_proofs/src/dev/failure/emitter.rs @@ -0,0 +1,164 @@ +use std::collections::BTreeMap; +use std::iter; + +use group::ff::Field; + +use super::FailureLocation; +use crate::{ + dev::{metadata, util}, + plonk::{Any, Expression}, +}; + +fn padded(p: char, width: usize, text: &str) -> String { + let pad = width - text.len(); + format!( + "{}{}{}", + iter::repeat(p).take(pad - pad / 2).collect::(), + text, + iter::repeat(p).take(pad / 2).collect::(), + ) +} + +/// Renders a cell layout around a given failure location. +/// +/// `highlight_row` is called at the end of each row, with the offset of the active row +/// (if `location` is in a region), and the rotation of the current row relative to the +/// active row. +pub(super) fn render_cell_layout( + prefix: &str, + location: &FailureLocation, + columns: &BTreeMap, + layout: &BTreeMap>, + highlight_row: impl Fn(Option, i32), +) { + let col_width = |cells: usize| cells.to_string().len() + 3; + + // If we are in a region, show rows at offsets relative to it. Otherwise, just show + // the rotations directly. + let offset = match location { + FailureLocation::InRegion { region, offset } => { + eprintln!("{}Cell layout in region '{}':", prefix, region.name); + eprint!("{} | Offset |", prefix); + Some(*offset as i32) + } + FailureLocation::OutsideRegion { row } => { + eprintln!("{}Cell layout at row {}:", prefix, row); + eprint!("{} |Rotation|", prefix); + None + } + }; + + // Print the assigned cells, and their region offset or rotation. + for (column, cells) in columns { + let width = col_width(*cells); + eprint!( + "{}|", + padded( + ' ', + width, + &format!( + "{}{}", + match column.column_type { + Any::Advice => "A", + Any::Fixed => "F", + Any::Instance => "I", + }, + column.index, + ) + ) + ); + } + eprintln!(); + eprint!("{} +--------+", prefix); + for cells in columns.values() { + eprint!("{}+", padded('-', col_width(*cells), "")); + } + eprintln!(); + for (rotation, row) in layout { + eprint!( + "{} |{}|", + prefix, + padded(' ', 8, &(offset.unwrap_or(0) + rotation).to_string()) + ); + for (col, cells) in columns { + let width = col_width(*cells); + eprint!( + "{}|", + padded( + ' ', + width, + row.get(col).map(|s| s.as_str()).unwrap_or_default() + ) + ); + } + highlight_row(offset, *rotation); + eprintln!(); + } +} + +pub(super) fn expression_to_string( + expr: &Expression, + layout: &BTreeMap>, +) -> String { + expr.evaluate( + &util::format_value, + &|_| panic!("virtual selectors are removed during optimization"), + &|query| { + if let Some(label) = layout + .get(&query.rotation.0) + .and_then(|row| row.get(&(Any::Fixed, query.column_index).into())) + { + label.clone() + } else if query.rotation.0 == 0 { + // This is most likely a merged selector + format!("S{}", query.index) + } else { + // No idea how we'd get here... + format!("F{}@{}", query.column_index, query.rotation.0) + } + }, + &|query| { + layout + .get(&query.rotation.0) + .unwrap() + .get(&(Any::Advice, query.column_index).into()) + .unwrap() + .clone() + }, + &|query| { + layout + .get(&query.rotation.0) + .unwrap() + .get(&(Any::Instance, query.column_index).into()) + .unwrap() + .clone() + }, + &|a| { + if a.contains(' ') { + format!("-({})", a) + } else { + format!("-{}", a) + } + }, + &|a, b| { + if let Some(b) = b.strip_prefix('-') { + format!("{} - {}", a, b) + } else { + format!("{} + {}", a, b) + } + }, + &|a, b| match (a.contains(' '), b.contains(' ')) { + (false, false) => format!("{} * {}", a, b), + (false, true) => format!("{} * ({})", a, b), + (true, false) => format!("({}) * {}", a, b), + (true, true) => format!("({}) * ({})", a, b), + }, + &|a, s| { + if a.contains(' ') { + format!("({}) * {}", a, util::format_value(s)) + } else { + format!("{} * {}", a, util::format_value(s)) + } + }, + ) +} diff --git a/halo2_proofs/src/dev/gates.rs b/halo2_proofs/src/dev/gates.rs index 2833c2dc87..be098ec129 100644 --- a/halo2_proofs/src/dev/gates.rs +++ b/halo2_proofs/src/dev/gates.rs @@ -119,9 +119,9 @@ impl CircuitGates { expression: constraint.evaluate( &util::format_value, &|selector| format!("S{}", selector.0), - &|_, column, rotation| format!("F{}@{}", column, rotation.0), - &|_, column, rotation| format!("A{}@{}", column, rotation.0), - &|_, column, rotation| format!("I{}@{}", column, rotation.0), + &|query| format!("F{}@{}", query.column_index, query.rotation.0), + &|query| format!("A{}@{}", query.column_index, query.rotation.0), + &|query| format!("I{}@{}", query.column_index, query.rotation.0), &|a| { if a.contains(' ') { format!("-({})", a) @@ -153,18 +153,18 @@ impl CircuitGates { queries: constraint.evaluate( &|_| BTreeSet::default(), &|selector| vec![format!("S{}", selector.0)].into_iter().collect(), - &|_, column, rotation| { - vec![format!("F{}@{}", column, rotation.0)] + &|query| { + vec![format!("F{}@{}", query.column_index, query.rotation.0)] .into_iter() .collect() }, - &|_, column, rotation| { - vec![format!("A{}@{}", column, rotation.0)] + &|query| { + vec![format!("A{}@{}", query.column_index, query.rotation.0)] .into_iter() .collect() }, - &|_, column, rotation| { - vec![format!("I{}@{}", column, rotation.0)] + &|query| { + vec![format!("I{}@{}", query.column_index, query.rotation.0)] .into_iter() .collect() }, @@ -192,9 +192,9 @@ impl CircuitGates { poly.evaluate( &|_| (0, 0, 0), &|_| (0, 0, 0), - &|_, _, _| (0, 0, 0), - &|_, _, _| (0, 0, 0), - &|_, _, _| (0, 0, 0), + &|_| (0, 0, 0), + &|_| (0, 0, 0), + &|_| (0, 0, 0), &|(a_n, a_a, a_m)| (a_n + 1, a_a, a_m), &|(a_n, a_a, a_m), (b_n, b_a, b_m)| (a_n + b_n, a_a + b_a + 1, a_m + b_m), &|(a_n, a_a, a_m), (b_n, b_a, b_m)| (a_n + b_n, a_a + b_a, a_m + b_m + 1), diff --git a/halo2_proofs/src/dev/graph.rs b/halo2_proofs/src/dev/graph.rs index e9ed027a38..18ef8154df 100644 --- a/halo2_proofs/src/dev/graph.rs +++ b/halo2_proofs/src/dev/graph.rs @@ -1,9 +1,12 @@ use ff::Field; use tabbycat::{AttrList, Edge, GraphBuilder, GraphType, Identity, StmtList}; -use crate::plonk::{ - Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed, - FloorPlanner, Instance, Selector, +use crate::{ + circuit::Value, + plonk::{ + Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed, + FloorPlanner, Instance, Selector, + }, }; pub mod layout; @@ -96,8 +99,8 @@ impl Assignment for Graph { Ok(()) } - fn query_instance(&self, _: Column, _: usize) -> Result, Error> { - Ok(None) + fn query_instance(&self, _: Column, _: usize) -> Result, Error> { + Ok(Value::unknown()) } fn assign_advice( @@ -108,7 +111,7 @@ impl Assignment for Graph { _: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> Value, VR: Into>, A: FnOnce() -> AR, AR: Into, @@ -125,7 +128,7 @@ impl Assignment for Graph { _: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> Value, VR: Into>, A: FnOnce() -> AR, AR: Into, @@ -149,7 +152,7 @@ impl Assignment for Graph { &mut self, _: Column, _: usize, - _: Option>, + _: Value>, ) -> Result<(), Error> { Ok(()) } diff --git a/halo2_proofs/src/dev/graph/layout.rs b/halo2_proofs/src/dev/graph/layout.rs index 612b9bab57..7d9d56c02f 100644 --- a/halo2_proofs/src/dev/graph/layout.rs +++ b/halo2_proofs/src/dev/graph/layout.rs @@ -7,10 +7,12 @@ use std::cmp; use std::collections::HashSet; use std::ops::Range; -use crate::circuit::layouter::RegionColumn; -use crate::plonk::{ - Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed, - FloorPlanner, Instance, Selector, +use crate::{ + circuit::{layouter::RegionColumn, Value}, + plonk::{ + Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed, + FloorPlanner, Instance, Selector, + }, }; /// Graphical renderer for circuit layouts. @@ -430,8 +432,8 @@ impl Assignment for Layout { Ok(()) } - fn query_instance(&self, _: Column, _: usize) -> Result, Error> { - Ok(None) + fn query_instance(&self, _: Column, _: usize) -> Result, Error> { + Ok(Value::unknown()) } fn assign_advice( @@ -442,7 +444,7 @@ impl Assignment for Layout { _: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> Value, VR: Into>, A: FnOnce() -> AR, AR: Into, @@ -459,7 +461,7 @@ impl Assignment for Layout { _: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> Value, VR: Into>, A: FnOnce() -> AR, AR: Into, @@ -483,7 +485,7 @@ impl Assignment for Layout { &mut self, _: Column, _: usize, - _: Option>, + _: Value>, ) -> Result<(), Error> { Ok(()) } diff --git a/halo2_proofs/src/dev/metadata.rs b/halo2_proofs/src/dev/metadata.rs index 8fdf10a862..d7d2443e7d 100644 --- a/halo2_proofs/src/dev/metadata.rs +++ b/halo2_proofs/src/dev/metadata.rs @@ -4,12 +4,12 @@ use crate::plonk::{self, Any}; use std::fmt; /// Metadata about a column within a circuit. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Column { /// The type of the column. - column_type: Any, + pub(super) column_type: Any, /// The index of the column. - index: usize, + pub(super) index: usize, } impl fmt::Display for Column { @@ -38,8 +38,8 @@ impl From> for Column { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct VirtualCell { name: &'static str, - column: Column, - rotation: i32, + pub(super) column: Column, + pub(super) rotation: i32, } impl From<(Column, i32)> for VirtualCell { @@ -87,10 +87,10 @@ impl fmt::Display for VirtualCell { pub struct Gate { /// The index of the active gate. These indices are assigned in the order in which /// `ConstraintSystem::create_gate` is called during `Circuit::configure`. - index: usize, + pub(super) index: usize, /// The name of the active gate. These are specified by the gate creator (such as /// a chip implementation), and is not enforced to be unique. - name: &'static str, + pub(super) name: &'static str, } impl fmt::Display for Gate { @@ -109,14 +109,14 @@ impl From<(usize, &'static str)> for Gate { #[derive(Debug, PartialEq)] pub struct Constraint { /// The gate containing the constraint. - gate: Gate, + pub(super) gate: Gate, /// The index of the polynomial constraint within the gate. These indices correspond /// to the order in which the constraints are returned from the closure passed to /// `ConstraintSystem::create_gate` during `Circuit::configure`. - index: usize, + pub(super) index: usize, /// The name of the constraint. This is specified by the gate creator (such as a chip /// implementation), and is not enforced to be unique. - name: &'static str, + pub(super) name: &'static str, } impl fmt::Display for Constraint { @@ -143,14 +143,14 @@ impl From<(Gate, usize, &'static str)> for Constraint { } /// Metadata about an assigned region within a circuit. -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Region { /// The index of the region. These indices are assigned in the order in which /// `Layouter::assign_region` is called during `Circuit::synthesize`. - index: usize, + pub(super) index: usize, /// The name of the region. This is specified by the region creator (such as a chip /// implementation), and is not enforced to be unique. - name: String, + pub(super) name: String, } impl fmt::Display for Region { diff --git a/halo2_proofs/src/dev/util.rs b/halo2_proofs/src/dev/util.rs index 4ab4121ca6..0ed25c0a36 100644 --- a/halo2_proofs/src/dev/util.rs +++ b/halo2_proofs/src/dev/util.rs @@ -3,12 +3,59 @@ use std::collections::BTreeMap; use group::ff::Field; use pairing::arithmetic::FieldExt; -use super::{metadata, Value}; +use super::{metadata, CellValue, Value}; use crate::{ - plonk::{Any, Expression, Gate, VirtualCell}, + plonk::{ + AdviceQuery, Any, Column, ColumnType, Expression, FixedQuery, Gate, InstanceQuery, + VirtualCell, + }, poly::Rotation, }; +pub(crate) struct AnyQuery { + /// Query index + pub index: usize, + /// Column type + pub column_type: Any, + /// Column index + pub column_index: usize, + /// Rotation of this query + pub rotation: Rotation, +} + +impl From for AnyQuery { + fn from(query: FixedQuery) -> Self { + Self { + index: query.index, + column_type: Any::Fixed, + column_index: query.column_index, + rotation: query.rotation, + } + } +} + +impl From for AnyQuery { + fn from(query: AdviceQuery) -> Self { + Self { + index: query.index, + column_type: Any::Advice, + column_index: query.column_index, + rotation: query.rotation, + } + } +} + +impl From for AnyQuery { + fn from(query: InstanceQuery) -> Self { + Self { + index: query.index, + column_type: Any::Instance, + column_index: query.column_index, + rotation: query.rotation, + } + } +} + pub(super) fn format_value(v: F) -> String { if v.is_zero_vartime() { "0".into() @@ -26,12 +73,43 @@ pub(super) fn format_value(v: F) -> String { } } -fn cell_value<'a, F: FieldExt>( +pub(super) fn load<'a, F: FieldExt, T: ColumnType, Q: Into + Copy>( + n: i32, + row: i32, + queries: &'a [(Column, Rotation)], + cells: &'a [Vec>], +) -> impl Fn(Q) -> Value + 'a { + move |query| { + let (column, at) = &queries[query.into().index]; + let resolved_row = (row + at.0) % n; + cells[column.index()][resolved_row as usize].into() + } +} + +pub(super) fn load_instance<'a, F: FieldExt, T: ColumnType, Q: Into + Copy>( + n: i32, + row: i32, + queries: &'a [(Column, Rotation)], + cells: &'a [Vec], +) -> impl Fn(Q) -> Value + 'a { + move |query| { + let (column, at) = &queries[query.into().index]; + let resolved_row = (row + at.0) % n; + Value::Real(cells[column.index()][resolved_row as usize]) + } +} + +fn cell_value<'a, F: FieldExt, Q: Into + Copy>( virtual_cells: &'a [VirtualCell], - column_type: Any, - load: impl Fn(usize, usize, Rotation) -> Value + 'a, -) -> impl Fn(usize, usize, Rotation) -> BTreeMap + 'a { - move |query_index, column_index, rotation| { + load: impl Fn(Q) -> Value + 'a, +) -> impl Fn(Q) -> BTreeMap + 'a { + move |query| { + let AnyQuery { + column_type, + column_index, + rotation, + .. + } = query.into(); virtual_cells .iter() .find(|c| { @@ -43,7 +121,7 @@ fn cell_value<'a, F: FieldExt>( .map(|cell| { ( cell.clone().into(), - match load(query_index, column_index, rotation) { + match load(query) { Value::Real(v) => format_value(v), Value::Poison => unreachable!(), }, @@ -57,17 +135,17 @@ fn cell_value<'a, F: FieldExt>( pub(super) fn cell_values<'a, F: FieldExt>( gate: &Gate, poly: &Expression, - load_fixed: impl Fn(usize, usize, Rotation) -> Value + 'a, - load_advice: impl Fn(usize, usize, Rotation) -> Value + 'a, - load_instance: impl Fn(usize, usize, Rotation) -> Value + 'a, + load_fixed: impl Fn(FixedQuery) -> Value + 'a, + load_advice: impl Fn(AdviceQuery) -> Value + 'a, + load_instance: impl Fn(InstanceQuery) -> Value + 'a, ) -> Vec<(metadata::VirtualCell, String)> { let virtual_cells = gate.queried_cells(); let cell_values = poly.evaluate( &|_| BTreeMap::default(), &|_| panic!("virtual selectors are removed during optimization"), - &cell_value(virtual_cells, Any::Fixed, load_fixed), - &cell_value(virtual_cells, Any::Advice, load_advice), - &cell_value(virtual_cells, Any::Instance, load_instance), + &cell_value(virtual_cells, load_fixed), + &cell_value(virtual_cells, load_advice), + &cell_value(virtual_cells, load_instance), &|a| a, &|mut a, mut b| { a.append(&mut b); diff --git a/halo2_proofs/src/plonk.rs b/halo2_proofs/src/plonk.rs index 1f7cb9301c..ed84a40edd 100644 --- a/halo2_proofs/src/plonk.rs +++ b/halo2_proofs/src/plonk.rs @@ -6,6 +6,7 @@ //! [plonk]: https://eprint.iacr.org/2019/953 use blake2b_simd::Params as Blake2bParams; +use group::ff::Field; use crate::arithmetic::{BaseExt, CurveAffine, FieldExt}; use crate::helpers::CurveRead; @@ -40,63 +41,60 @@ use self::evaluation::Evaluator; /// This is a verifying key which allows for the verification of proofs for a /// particular circuit. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct VerifyingKey { domain: EvaluationDomain, fixed_commitments: Vec, permutation: permutation::VerifyingKey, cs: ConstraintSystem, + /// Cached maximum degree of `cs` (which doesn't change after construction). + cs_degree: usize, + /// The representative of this `VerifyingKey` in transcripts. + transcript_repr: C::Scalar, } impl VerifyingKey { - /// Writes a verifying key to a buffer. - pub fn write(&self, writer: &mut W) -> io::Result<()> { - for commitment in &self.fixed_commitments { - writer.write_all(commitment.to_bytes().as_ref())?; - } - self.permutation.write(writer)?; - - Ok(()) - } - - /// Reads a verification key from a buffer. - pub fn read>( - reader: &mut R, - params: &Params, - ) -> io::Result { - let (domain, cs, _) = keygen::create_domain::(params); - - let fixed_commitments: Vec<_> = (0..cs.num_fixed_columns) - .map(|_| C::read(reader)) - .collect::>()?; - - let permutation = permutation::VerifyingKey::read(reader, &cs.permutation)?; - - Ok(VerifyingKey { + fn from_parts( + domain: EvaluationDomain, + fixed_commitments: Vec, + permutation: permutation::VerifyingKey, + cs: ConstraintSystem, + ) -> Self { + // Compute cached values. + let cs_degree = cs.degree(); + + let mut vk = Self { domain, fixed_commitments, permutation, cs, - }) - } + cs_degree, + // Temporary, this is not pinned. + transcript_repr: C::Scalar::zero(), + }; - /// Hashes a verification key into a transcript. - pub fn hash_into, T: Transcript>( - &self, - transcript: &mut T, - ) -> io::Result<()> { let mut hasher = Blake2bParams::new() .hash_length(64) .personal(b"Halo2-Verify-Key") .to_state(); - let s = format!("{:?}", self.pinned()); + let s = format!("{:?}", vk.pinned()); hasher.update(&(s.len() as u64).to_le_bytes()); hasher.update(s.as_bytes()); // Hash in final Blake2bState - transcript.common_scalar(C::Scalar::from_bytes_wide(hasher.finalize().as_array()))?; + vk.transcript_repr = C::Scalar::from_bytes_wide(hasher.finalize().as_array()); + + vk + } + + /// Hashes a verification key into a transcript. + pub fn hash_into, T: Transcript>( + &self, + transcript: &mut T, + ) -> io::Result<()> { + transcript.common_scalar(self.transcript_repr)?; Ok(()) } @@ -129,7 +127,7 @@ pub struct PinnedVerificationKey<'a, C: CurveAffine> { } /// This is a proving key which allows for the creation of proofs for a /// particular circuit. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ProvingKey { vk: VerifyingKey, l0: Polynomial, diff --git a/halo2_proofs/src/plonk/assigned.rs b/halo2_proofs/src/plonk/assigned.rs index dacce06e96..f4a65f520c 100644 --- a/halo2_proofs/src/plonk/assigned.rs +++ b/halo2_proofs/src/plonk/assigned.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, Mul, Neg, Sub}; +use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use group::ff::Field; @@ -17,6 +17,12 @@ pub enum Assigned { Rational(F, F), } +impl From<&Assigned> for Assigned { + fn from(val: &Assigned) -> Self { + *val + } +} + impl From<&F> for Assigned { fn from(numerator: &F) -> Self { Assigned::Trivial(*numerator) @@ -35,6 +41,36 @@ impl From<(F, F)> for Assigned { } } +impl PartialEq for Assigned { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + // At least one side is directly zero. + (Self::Zero, Self::Zero) => true, + (Self::Zero, x) | (x, Self::Zero) => x.is_zero_vartime(), + + // One side is x/0 which maps to zero. + (Self::Rational(_, denominator), x) | (x, Self::Rational(_, denominator)) + if denominator.is_zero_vartime() => + { + x.is_zero_vartime() + } + + // Okay, we need to do some actual math... + (Self::Trivial(lhs), Self::Trivial(rhs)) => lhs == rhs, + (Self::Trivial(x), Self::Rational(numerator, denominator)) + | (Self::Rational(numerator, denominator), Self::Trivial(x)) => { + &(*x * denominator) == numerator + } + ( + Self::Rational(lhs_numerator, lhs_denominator), + Self::Rational(rhs_numerator, rhs_denominator), + ) => *lhs_numerator * rhs_denominator == *lhs_denominator * rhs_numerator, + } + } +} + +impl Eq for Assigned {} + impl Neg for Assigned { type Output = Assigned; fn neg(self) -> Self::Output { @@ -46,6 +82,13 @@ impl Neg for Assigned { } } +impl Neg for &Assigned { + type Output = Assigned; + fn neg(self) -> Self::Output { + -*self + } +} + impl Add for Assigned { type Output = Assigned; fn add(self, rhs: Assigned) -> Assigned { @@ -85,6 +128,46 @@ impl Add for Assigned { } } +impl Add for &Assigned { + type Output = Assigned; + fn add(self, rhs: F) -> Assigned { + *self + rhs + } +} + +impl Add<&Assigned> for Assigned { + type Output = Assigned; + fn add(self, rhs: &Self) -> Assigned { + self + *rhs + } +} + +impl Add> for &Assigned { + type Output = Assigned; + fn add(self, rhs: Assigned) -> Assigned { + *self + rhs + } +} + +impl Add<&Assigned> for &Assigned { + type Output = Assigned; + fn add(self, rhs: &Assigned) -> Assigned { + *self + *rhs + } +} + +impl AddAssign for Assigned { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs; + } +} + +impl AddAssign<&Assigned> for Assigned { + fn add_assign(&mut self, rhs: &Self) { + *self = *self + rhs; + } +} + impl Sub for Assigned { type Output = Assigned; fn sub(self, rhs: Assigned) -> Assigned { @@ -99,6 +182,46 @@ impl Sub for Assigned { } } +impl Sub for &Assigned { + type Output = Assigned; + fn sub(self, rhs: F) -> Assigned { + *self - rhs + } +} + +impl Sub<&Assigned> for Assigned { + type Output = Assigned; + fn sub(self, rhs: &Self) -> Assigned { + self - *rhs + } +} + +impl Sub> for &Assigned { + type Output = Assigned; + fn sub(self, rhs: Assigned) -> Assigned { + *self - rhs + } +} + +impl Sub<&Assigned> for &Assigned { + type Output = Assigned; + fn sub(self, rhs: &Assigned) -> Assigned { + *self - *rhs + } +} + +impl SubAssign for Assigned { + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs; + } +} + +impl SubAssign<&Assigned> for Assigned { + fn sub_assign(&mut self, rhs: &Self) { + *self = *self - rhs; + } +} + impl Mul for Assigned { type Output = Assigned; fn mul(self, rhs: Assigned) -> Assigned { @@ -127,6 +250,32 @@ impl Mul for Assigned { } } +impl Mul for &Assigned { + type Output = Assigned; + fn mul(self, rhs: F) -> Assigned { + *self * rhs + } +} + +impl Mul<&Assigned> for Assigned { + type Output = Assigned; + fn mul(self, rhs: &Assigned) -> Assigned { + self * *rhs + } +} + +impl MulAssign for Assigned { + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl MulAssign<&Assigned> for Assigned { + fn mul_assign(&mut self, rhs: &Self) { + *self = *self * rhs; + } +} + impl Assigned { /// Returns the numerator. pub fn numerator(&self) -> F { @@ -146,6 +295,48 @@ impl Assigned { } } + /// Returns true iff this element is zero. + pub fn is_zero_vartime(&self) -> bool { + match self { + Self::Zero => true, + Self::Trivial(x) => x.is_zero_vartime(), + // Assigned maps x/0 -> 0. + Self::Rational(numerator, denominator) => { + numerator.is_zero_vartime() || denominator.is_zero_vartime() + } + } + } + + /// Doubles this element. + #[must_use] + pub fn double(&self) -> Self { + match self { + Self::Zero => Self::Zero, + Self::Trivial(x) => Self::Trivial(x.double()), + Self::Rational(numerator, denominator) => { + Self::Rational(numerator.double(), *denominator) + } + } + } + + /// Squares this element. + #[must_use] + pub fn square(&self) -> Self { + match self { + Self::Zero => Self::Zero, + Self::Trivial(x) => Self::Trivial(x.square()), + Self::Rational(numerator, denominator) => { + Self::Rational(numerator.square(), denominator.square()) + } + } + } + + /// Cubes this element. + #[must_use] + pub fn cube(&self) -> Self { + self.square() * self + } + /// Inverts this assigned value (taking the inverse of zero to be zero). pub fn invert(&self) -> Self { match self { @@ -254,26 +445,108 @@ mod tests { #[cfg(test)] mod proptests { use std::{ + cmp, convert::TryFrom, - ops::{Add, Mul, Sub}, + ops::{Add, Mul, Neg, Sub}, }; + use group::ff::Field; use pairing::{arithmetic::FieldExt, bn256::Fr as Fp}; use proptest::{collection::vec, prelude::*, sample::select}; use super::Assigned; + trait UnaryOperand: Neg { + fn double(&self) -> Self; + fn square(&self) -> Self; + fn cube(&self) -> Self; + fn inv0(&self) -> Self; + } + + impl UnaryOperand for F { + fn double(&self) -> Self { + self.double() + } + + fn square(&self) -> Self { + self.square() + } + + fn cube(&self) -> Self { + self.cube() + } + + fn inv0(&self) -> Self { + self.invert().unwrap_or(F::zero()) + } + } + + impl UnaryOperand for Assigned { + fn double(&self) -> Self { + self.double() + } + + fn square(&self) -> Self { + self.square() + } + + fn cube(&self) -> Self { + self.cube() + } + + fn inv0(&self) -> Self { + self.invert() + } + } + #[derive(Clone, Debug)] - enum Operation { + enum UnaryOperator { + Neg, + Double, + Square, + Cube, + Inv0, + } + + const UNARY_OPERATORS: &[UnaryOperator] = &[ + UnaryOperator::Neg, + UnaryOperator::Double, + UnaryOperator::Square, + UnaryOperator::Cube, + UnaryOperator::Inv0, + ]; + + impl UnaryOperator { + fn apply(&self, a: F) -> F { + match self { + Self::Neg => -a, + Self::Double => a.double(), + Self::Square => a.square(), + Self::Cube => a.cube(), + Self::Inv0 => a.inv0(), + } + } + } + + trait BinaryOperand: Sized + Add + Sub + Mul {} + impl BinaryOperand for F {} + impl BinaryOperand for Assigned {} + + #[derive(Clone, Debug)] + enum BinaryOperator { Add, Sub, Mul, } - const OPERATIONS: &[Operation] = &[Operation::Add, Operation::Sub, Operation::Mul]; + const BINARY_OPERATORS: &[BinaryOperator] = &[ + BinaryOperator::Add, + BinaryOperator::Sub, + BinaryOperator::Mul, + ]; - impl Operation { - fn apply + Sub + Mul>(&self, a: F, b: F) -> F { + impl BinaryOperator { + fn apply(&self, a: F, b: F) -> F { match self { Self::Add => a + b, Self::Sub => a - b, @@ -282,6 +555,12 @@ mod proptests { } } + #[derive(Clone, Debug)] + enum Operator { + Unary(UnaryOperator), + Binary(BinaryOperator), + } + prop_compose! { /// Use narrow that can be easily reduced. fn arb_element()(val in any::()) -> Fp { @@ -299,21 +578,44 @@ mod proptests { /// Generates half of the denominators as zero to represent a deferred inversion. fn arb_rational()( numerator in arb_element(), - denominator in prop_oneof![Just(Fp::zero()), arb_element()], + denominator in prop_oneof![ + 1 => Just(Fp::zero()), + 2 => arb_element(), + ], ) -> Assigned { Assigned::Rational(numerator, denominator) } } + prop_compose! { + fn arb_operators(num_unary: usize, num_binary: usize)( + unary in vec(select(UNARY_OPERATORS), num_unary), + binary in vec(select(BINARY_OPERATORS), num_binary), + ) -> Vec { + unary.into_iter() + .map(Operator::Unary) + .chain(binary.into_iter().map(Operator::Binary)) + .collect() + } + } + prop_compose! { fn arb_testcase()( - num_operations in 1usize..5, + num_unary in 0usize..5, + num_binary in 0usize..5, )( values in vec( - prop_oneof![Just(Assigned::Zero), arb_trivial(), arb_rational()], - num_operations + 1), - operations in vec(select(OPERATIONS), num_operations), - ) -> (Vec>, Vec) { + prop_oneof![ + 1 => Just(Assigned::Zero), + 2 => arb_trivial(), + 2 => arb_rational(), + ], + // Ensure that: + // - we have at least one value to apply unary operators to. + // - we can apply every binary operator pairwise sequentially. + cmp::max(if num_unary > 0 { 1 } else { 0 }, num_binary + 1)), + operations in arb_operators(num_unary, num_binary).prop_shuffle(), + ) -> (Vec>, Vec) { (values, operations) } } @@ -325,14 +627,36 @@ mod proptests { let elements: Vec<_> = values.iter().cloned().map(|v| v.evaluate()).collect(); // Apply the operations to both the deferred and evaluated values. - let deferred_result = { - let mut ops = operations.iter(); - values.into_iter().reduce(|a, b| ops.next().unwrap().apply(a, b)).unwrap() - }; - let evaluated_result = { - let mut ops = operations.iter(); - elements.into_iter().reduce(|a, b| ops.next().unwrap().apply(a, b)).unwrap() - }; + fn evaluate( + items: Vec, + operators: &[Operator], + ) -> F { + let mut ops = operators.iter(); + + // Process all binary operators. We are guaranteed to have exactly as many + // binary operators as we need calls to the reduction closure. + let mut res = items.into_iter().reduce(|mut a, b| loop { + match ops.next() { + Some(Operator::Unary(op)) => a = op.apply(a), + Some(Operator::Binary(op)) => break op.apply(a, b), + None => unreachable!(), + } + }).unwrap(); + + // Process any unary operators that weren't handled in the reduce() call + // above (either if we only had one item, or there were unary operators + // after the last binary operator). We are guaranteed to have no binary + // operators remaining at this point. + loop { + match ops.next() { + Some(Operator::Unary(op)) => res = op.apply(res), + Some(Operator::Binary(_)) => unreachable!(), + None => break res, + } + } + } + let deferred_result = evaluate(values, &operations); + let evaluated_result = evaluate(elements, &operations); // The two should be equal, i.e. deferred inversion should commute with the // list of operations. diff --git a/halo2_proofs/src/plonk/circuit.rs b/halo2_proofs/src/plonk/circuit.rs index 258782b1ed..9c41a686b4 100644 --- a/halo2_proofs/src/plonk/circuit.rs +++ b/halo2_proofs/src/plonk/circuit.rs @@ -7,8 +7,10 @@ use std::{ }; use super::{lookup, permutation, Assigned, Error}; -use crate::circuit::Layouter; -use crate::{circuit::Region, poly::Rotation}; +use crate::{ + circuit::{Layouter, Region, Value}, + poly::Rotation, +}; mod compress_selectors; @@ -227,7 +229,11 @@ impl TryFrom> for Column { /// Selectors are disabled on all rows by default, and must be explicitly enabled on each /// row when required: /// ``` -/// use halo2_proofs::{arithmetic::FieldExt, circuit::{Chip, Layouter}, plonk::{Advice, Column, Error, Selector}}; +/// use halo2_proofs::{ +/// arithmetic::FieldExt, +/// circuit::{Chip, Layouter, Value}, +/// plonk::{Advice, Column, Error, Selector}, +/// }; /// # use ff::Field; /// # use halo2_proofs::plonk::Fixed; /// @@ -241,8 +247,8 @@ impl TryFrom> for Column { /// let config = chip.config(); /// # let config: Config = todo!(); /// layouter.assign_region(|| "bar", |mut region| { -/// region.assign_advice(|| "a", config.a, 0, || Ok(F::one()))?; -/// region.assign_advice(|| "a", config.b, 1, || Ok(F::one()))?; +/// region.assign_advice(|| "a", config.a, 0, || Value::known(F::one()))?; +/// region.assign_advice(|| "a", config.b, 1, || Value::known(F::one()))?; /// config.s.enable(&mut region, 1) /// })?; /// Ok(()) @@ -264,6 +270,39 @@ impl Selector { } } +/// Query of fixed column at a certain relative location +#[derive(Copy, Clone, Debug)] +pub struct FixedQuery { + /// Query index + pub(crate) index: usize, + /// Column index + pub(crate) column_index: usize, + /// Rotation of this query + pub(crate) rotation: Rotation, +} + +/// Query of advice column at a certain relative location +#[derive(Copy, Clone, Debug)] +pub struct AdviceQuery { + /// Query index + pub(crate) index: usize, + /// Column index + pub(crate) column_index: usize, + /// Rotation of this query + pub(crate) rotation: Rotation, +} + +/// Query of instance column at a certain relative location +#[derive(Copy, Clone, Debug)] +pub struct InstanceQuery { + /// Query index + pub(crate) index: usize, + /// Column index + pub(crate) column_index: usize, + /// Rotation of this query + pub(crate) rotation: Rotation, +} + /// A fixed column of a lookup table. /// /// A lookup table can be loaded into this column via [`Layouter::assign_table`]. Columns @@ -330,7 +369,7 @@ pub trait Assignment { /// Queries the cell of an instance column at a particular absolute row. /// /// Returns the cell's value, if known. - fn query_instance(&self, column: Column, row: usize) -> Result, Error>; + fn query_instance(&self, column: Column, row: usize) -> Result, Error>; /// Assign an advice column value (witness) fn assign_advice( @@ -341,7 +380,7 @@ pub trait Assignment { to: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> Value, VR: Into>, A: FnOnce() -> AR, AR: Into; @@ -355,7 +394,7 @@ pub trait Assignment { to: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> Value, VR: Into>, A: FnOnce() -> AR, AR: Into; @@ -374,7 +413,7 @@ pub trait Assignment { &mut self, column: Column, row: usize, - to: Option>, + to: Value>, ) -> Result<(), Error>; /// Creates a new (sub)namespace and enters into it. @@ -443,39 +482,18 @@ pub trait Circuit { } /// Low-degree expression representing an identity that must hold over the committed columns. -#[derive(Clone, Debug)] +#[derive(Clone)] pub enum Expression { /// This is a constant polynomial Constant(F), /// This is a virtual selector Selector(Selector), /// This is a fixed column queried at a certain relative location - Fixed { - /// Query index - query_index: usize, - /// Column index - column_index: usize, - /// Rotation of this query - rotation: Rotation, - }, + Fixed(FixedQuery), /// This is an advice (witness) column queried at a certain relative location - Advice { - /// Query index - query_index: usize, - /// Column index - column_index: usize, - /// Rotation of this query - rotation: Rotation, - }, + Advice(AdviceQuery), /// This is an instance (external) column queried at a certain relative location - Instance { - /// Query index - query_index: usize, - /// Column index - column_index: usize, - /// Rotation of this query - rotation: Rotation, - }, + Instance(InstanceQuery), /// This is a negated polynomial Negated(Box>), /// This is the sum of two polynomials @@ -493,9 +511,9 @@ impl Expression { &self, constant: &impl Fn(F) -> T, selector_column: &impl Fn(Selector) -> T, - fixed_column: &impl Fn(usize, usize, Rotation) -> T, - advice_column: &impl Fn(usize, usize, Rotation) -> T, - instance_column: &impl Fn(usize, usize, Rotation) -> T, + fixed_column: &impl Fn(FixedQuery) -> T, + advice_column: &impl Fn(AdviceQuery) -> T, + instance_column: &impl Fn(InstanceQuery) -> T, negated: &impl Fn(T) -> T, sum: &impl Fn(T, T) -> T, product: &impl Fn(T, T) -> T, @@ -504,21 +522,9 @@ impl Expression { match self { Expression::Constant(scalar) => constant(*scalar), Expression::Selector(selector) => selector_column(*selector), - Expression::Fixed { - query_index, - column_index, - rotation, - } => fixed_column(*query_index, *column_index, *rotation), - Expression::Advice { - query_index, - column_index, - rotation, - } => advice_column(*query_index, *column_index, *rotation), - Expression::Instance { - query_index, - column_index, - rotation, - } => instance_column(*query_index, *column_index, *rotation), + Expression::Fixed(query) => fixed_column(*query), + Expression::Advice(query) => advice_column(*query), + Expression::Instance(query) => instance_column(*query), Expression::Negated(a) => { let a = a.evaluate( constant, @@ -807,9 +813,9 @@ impl Expression { self.evaluate( &|_| false, &|selector| selector.is_simple(), - &|_, _, _| false, - &|_, _, _| false, - &|_, _, _| false, + &|_| false, + &|_| false, + &|_| false, &|a| a, &|a, b| a || b, &|a, b| a || b, @@ -834,9 +840,9 @@ impl Expression { None } }, - &|_, _, _| None, - &|_, _, _| None, - &|_, _, _| None, + &|_| None, + &|_| None, + &|_| None, &|a| a, &op, &op, @@ -845,6 +851,52 @@ impl Expression { } } +impl std::fmt::Debug for Expression { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Expression::Constant(scalar) => f.debug_tuple("Constant").field(scalar).finish(), + Expression::Selector(selector) => f.debug_tuple("Selector").field(selector).finish(), + // Skip enum variant and print query struct directly to maintain backwards compatibility. + Expression::Fixed(FixedQuery { + index, + column_index, + rotation, + }) => f + .debug_struct("Fixed") + .field("query_index", index) + .field("column_index", column_index) + .field("rotation", rotation) + .finish(), + Expression::Advice(AdviceQuery { + index, + column_index, + rotation, + }) => f + .debug_struct("Advice") + .field("query_index", index) + .field("column_index", column_index) + .field("rotation", rotation) + .finish(), + Expression::Instance(InstanceQuery { + index, + column_index, + rotation, + }) => f + .debug_struct("Instance") + .field("query_index", index) + .field("column_index", column_index) + .field("rotation", rotation) + .finish(), + Expression::Negated(poly) => f.debug_tuple("Negated").field(poly).finish(), + Expression::Sum(a, b) => f.debug_tuple("Sum").field(a).field(b).finish(), + Expression::Product(a, b) => f.debug_tuple("Product").field(a).field(b).finish(), + Expression::Scaled(poly, scalar) => { + f.debug_tuple("Scaled").field(poly).field(scalar).finish() + } + } + } +} + impl Neg for Expression { type Output = Expression; fn neg(self) -> Self::Output { @@ -938,6 +990,88 @@ impl From> for Vec> { } } +/// A set of polynomial constraints with a common selector. +/// +/// ``` +/// use halo2_proofs::{pasta::Fp, plonk::{Constraints, Expression}, poly::Rotation}; +/// # use halo2_proofs::plonk::ConstraintSystem; +/// +/// # let mut meta = ConstraintSystem::::default(); +/// let a = meta.advice_column(); +/// let b = meta.advice_column(); +/// let c = meta.advice_column(); +/// let s = meta.selector(); +/// +/// meta.create_gate("foo", |meta| { +/// let next = meta.query_advice(a, Rotation::next()); +/// let a = meta.query_advice(a, Rotation::cur()); +/// let b = meta.query_advice(b, Rotation::cur()); +/// let c = meta.query_advice(c, Rotation::cur()); +/// let s_ternary = meta.query_selector(s); +/// +/// let one_minus_a = Expression::Constant(Fp::one()) - a.clone(); +/// +/// Constraints::with_selector( +/// s_ternary, +/// std::array::IntoIter::new([ +/// ("a is boolean", a.clone() * one_minus_a.clone()), +/// ("next == a ? b : c", next - (a * b + one_minus_a * c)), +/// ]), +/// ) +/// }); +/// ``` +/// +/// Note that the use of `std::array::IntoIter::new` is only necessary if you need to +/// support Rust 1.51 or 1.52. If your minimum supported Rust version is 1.53 or greater, +/// you can pass an array directly. +#[derive(Debug)] +pub struct Constraints>, Iter: IntoIterator> { + selector: Expression, + constraints: Iter, +} + +impl>, Iter: IntoIterator> Constraints { + /// Constructs a set of constraints that are controlled by the given selector. + /// + /// Each constraint `c` in `iterator` will be converted into the constraint + /// `selector * c`. + pub fn with_selector(selector: Expression, constraints: Iter) -> Self { + Constraints { + selector, + constraints, + } + } +} + +fn apply_selector_to_constraint>>( + (selector, c): (Expression, C), +) -> Constraint { + let constraint: Constraint = c.into(); + Constraint { + name: constraint.name, + poly: selector * constraint.poly, + } +} + +type ApplySelectorToConstraint = fn((Expression, C)) -> Constraint; +type ConstraintsIterator = std::iter::Map< + std::iter::Zip>, I>, + ApplySelectorToConstraint, +>; + +impl>, Iter: IntoIterator> IntoIterator + for Constraints +{ + type Item = Constraint; + type IntoIter = ConstraintsIterator; + + fn into_iter(self) -> Self::IntoIter { + std::iter::repeat(self.selector) + .zip(self.constraints.into_iter()) + .map(apply_selector_to_constraint) + } +} + #[derive(Clone, Debug)] pub(crate) struct Gate { name: &'static str, @@ -979,7 +1113,12 @@ pub struct ConstraintSystem { pub(crate) num_advice_columns: usize, pub(crate) num_instance_columns: usize, pub(crate) num_selectors: usize, + + /// This is a cached vector that maps virtual selectors to the concrete + /// fixed column that they were compressed into. This is just used by dev + /// tooling right now. pub(crate) selector_map: Vec>, + pub(crate) gates: Vec>, pub(crate) advice_queries: Vec<(Column, Rotation)>, // Contains an integer for each advice column @@ -1011,7 +1150,6 @@ pub struct PinnedConstraintSystem<'a, F: Field> { num_advice_columns: &'a usize, num_instance_columns: &'a usize, num_selectors: &'a usize, - selector_map: &'a [Column], gates: PinnedGates<'a, F>, advice_queries: &'a Vec<(Column, Rotation)>, instance_queries: &'a Vec<(Column, Rotation)>, @@ -1063,7 +1201,6 @@ impl ConstraintSystem { num_advice_columns: &self.num_advice_columns, num_instance_columns: &self.num_instance_columns, num_selectors: &self.num_selectors, - selector_map: &self.selector_map, gates: PinnedGates(&self.gates), fixed_queries: &self.fixed_queries, advice_queries: &self.advice_queries, @@ -1333,11 +1470,11 @@ impl ConstraintSystem { || { let column = self.fixed_column(); new_columns.push(column); - Expression::Fixed { - query_index: self.query_fixed_index(column, Rotation::cur()), + Expression::Fixed(FixedQuery { + index: self.query_fixed_index(column, Rotation::cur()), column_index: column.index, rotation: Rotation::cur(), - } + }) }, ); @@ -1374,21 +1511,9 @@ impl ConstraintSystem { selector_replacements[selector.0].clone() }, - &|query_index, column_index, rotation| Expression::Fixed { - query_index, - column_index, - rotation, - }, - &|query_index, column_index, rotation| Expression::Advice { - query_index, - column_index, - rotation, - }, - &|query_index, column_index, rotation| Expression::Instance { - query_index, - column_index, - rotation, - }, + &|query| Expression::Fixed(query), + &|query| Expression::Advice(query), + &|query| Expression::Instance(query), &|a| -a, &|a, b| a + b, &|a, b| a * b, @@ -1572,31 +1697,31 @@ impl<'a, F: Field> VirtualCells<'a, F> { /// Query a fixed column at a relative position pub fn query_fixed(&mut self, column: Column, at: Rotation) -> Expression { self.queried_cells.push((column, at).into()); - Expression::Fixed { - query_index: self.meta.query_fixed_index(column, at), + Expression::Fixed(FixedQuery { + index: self.meta.query_fixed_index(column, at), column_index: column.index, rotation: at, - } + }) } /// Query an advice column at a relative position pub fn query_advice(&mut self, column: Column, at: Rotation) -> Expression { self.queried_cells.push((column, at).into()); - Expression::Advice { - query_index: self.meta.query_advice_index(column, at), + Expression::Advice(AdviceQuery { + index: self.meta.query_advice_index(column, at), column_index: column.index, rotation: at, - } + }) } /// Query an instance column at a relative position pub fn query_instance(&mut self, column: Column, at: Rotation) -> Expression { self.queried_cells.push((column, at).into()); - Expression::Instance { - query_index: self.meta.query_instance_index(column, at), + Expression::Instance(InstanceQuery { + index: self.meta.query_instance_index(column, at), column_index: column.index, rotation: at, - } + }) } /// Query an Any column at a relative position diff --git a/halo2_proofs/src/plonk/circuit/compress_selectors.rs b/halo2_proofs/src/plonk/circuit/compress_selectors.rs index 42850bd7e3..3e586989d3 100644 --- a/halo2_proofs/src/plonk/circuit/compress_selectors.rs +++ b/halo2_proofs/src/plonk/circuit/compress_selectors.rs @@ -232,7 +232,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::poly::Rotation; + use crate::{plonk::FixedQuery, poly::Rotation}; use pairing::bn256::Fr as Fp; use proptest::collection::{vec, SizeRange}; use proptest::prelude::*; @@ -283,11 +283,11 @@ mod tests { let mut query = 0; let (combination_assignments, selector_assignments) = process::(selectors.clone(), max_degree, || { - let tmp = Expression::Fixed { - query_index: query, + let tmp = Expression::Fixed(FixedQuery { + index: query, column_index: query, rotation: Rotation::cur(), - }; + }); query += 1; tmp }); @@ -321,13 +321,13 @@ mod tests { let eval = selector.expression.evaluate( &|c| c, &|_| panic!("should not occur in returned expressions"), - &|query_index, _, _| { + &|query| { // Should be the correct combination in the expression - assert_eq!(selector.combination_index, query_index); + assert_eq!(selector.combination_index, query.index); assignment }, - &|_, _, _| panic!("should not occur in returned expressions"), - &|_, _, _| panic!("should not occur in returned expressions"), + &|_| panic!("should not occur in returned expressions"), + &|_| panic!("should not occur in returned expressions"), &|a| -a, &|a, b| a + b, &|a, b| a * b, diff --git a/halo2_proofs/src/plonk/keygen.rs b/halo2_proofs/src/plonk/keygen.rs index 3115be70fc..40a1c9ae58 100644 --- a/halo2_proofs/src/plonk/keygen.rs +++ b/halo2_proofs/src/plonk/keygen.rs @@ -13,17 +13,16 @@ use super::{ evaluation::Evaluator, permutation, Assigned, Error, LagrangeCoeff, Polynomial, ProvingKey, VerifyingKey, }; -use crate::{arithmetic::CurveAffine, poly::batch_invert_assigned}; use crate::{ - plonk::Expression, + arithmetic::{CurveAffine, parallelize}, + circuit::Value, poly::{ + batch_invert_assigned, commitment::{Blind, Params}, EvaluationDomain, Rotation, }, }; -use crate::arithmetic::parallelize; - pub(crate) fn create_domain( params: &Params, ) -> ( @@ -84,13 +83,13 @@ impl Assignment for Assembly { Ok(()) } - fn query_instance(&self, _: Column, row: usize) -> Result, Error> { + fn query_instance(&self, _: Column, row: usize) -> Result, Error> { if !self.usable_rows.contains(&row) { return Err(Error::not_enough_rows_available(self.k)); } // There is no instance in this context. - Ok(None) + Ok(Value::unknown()) } fn assign_advice( @@ -101,7 +100,7 @@ impl Assignment for Assembly { _: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> Value, VR: Into>, A: FnOnce() -> AR, AR: Into, @@ -118,7 +117,7 @@ impl Assignment for Assembly { to: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> Value, VR: Into>, A: FnOnce() -> AR, AR: Into, @@ -131,7 +130,7 @@ impl Assignment for Assembly { .fixed .get_mut(column.index()) .and_then(|v| v.get_mut(row)) - .ok_or(Error::BoundsFailure)? = to()?.into(); + .ok_or(Error::BoundsFailure)? = to().into_field().assign()?; Ok(()) } @@ -155,7 +154,7 @@ impl Assignment for Assembly { &mut self, column: Column, from_row: usize, - to: Option>, + to: Value>, ) -> Result<(), Error> { if !self.usable_rows.contains(&from_row) { return Err(Error::not_enough_rows_available(self.k)); @@ -166,8 +165,9 @@ impl Assignment for Assembly { .get_mut(column.index()) .ok_or(Error::BoundsFailure)?; + let filler = to.assign()?; for row in self.usable_rows.clone().skip(from_row) { - col[row] = to.ok_or(Error::Synthesis)?; + col[row] = filler; } Ok(()) @@ -235,12 +235,12 @@ where .map(|poly| params.commit_lagrange(poly).to_affine()) .collect(); - Ok(VerifyingKey { + Ok(VerifyingKey::from_parts( domain, fixed_commitments, - permutation: permutation_vk, + permutation_vk, cs, - }) + )) } /// Generate a `ProvingKey` from a `VerifyingKey` and an instance of `Circuit`. diff --git a/halo2_proofs/src/plonk/lookup/verifier.rs b/halo2_proofs/src/plonk/lookup/verifier.rs index 94fe650fe7..ed0d5d00dd 100644 --- a/halo2_proofs/src/plonk/lookup/verifier.rs +++ b/halo2_proofs/src/plonk/lookup/verifier.rs @@ -119,9 +119,9 @@ impl Evaluated { expression.evaluate( &|scalar| scalar, &|_| panic!("virtual selectors are removed during optimization"), - &|index, _, _| fixed_evals[index], - &|index, _, _| advice_evals[index], - &|index, _, _| instance_evals[index], + &|query| fixed_evals[query.index], + &|query| advice_evals[query.index], + &|query| instance_evals[query.index], &|a| -a, &|a, b| a + &b, &|a, b| a * &b, diff --git a/halo2_proofs/src/plonk/permutation.rs b/halo2_proofs/src/plonk/permutation.rs index 7ed0f87698..23ade0d3aa 100644 --- a/halo2_proofs/src/plonk/permutation.rs +++ b/halo2_proofs/src/plonk/permutation.rs @@ -73,30 +73,13 @@ impl Argument { } /// The verifying key for a single permutation argument. -#[derive(Debug)] +#[derive(Clone, Debug)] pub(crate) struct VerifyingKey { commitments: Vec, } -impl VerifyingKey { - pub(crate) fn write(&self, writer: &mut W) -> io::Result<()> { - for commitment in &self.commitments { - writer.write_all(commitment.to_bytes().as_ref())?; - } - - Ok(()) - } - - pub(crate) fn read(reader: &mut R, argument: &Argument) -> io::Result { - let commitments = (0..argument.columns.len()) - .map(|_| C::read(reader)) - .collect::, _>>()?; - Ok(VerifyingKey { commitments }) - } -} - /// The proving key for a single permutation argument. -#[derive(Debug)] +#[derive(Clone, Debug)] pub(crate) struct ProvingKey { permutations: Vec>, polys: Vec>, diff --git a/halo2_proofs/src/plonk/permutation/prover.rs b/halo2_proofs/src/plonk/permutation/prover.rs index 3a4eee280b..e622034a65 100644 --- a/halo2_proofs/src/plonk/permutation/prover.rs +++ b/halo2_proofs/src/plonk/permutation/prover.rs @@ -63,8 +63,8 @@ impl Argument { // We need to multiply by z(X) and (1 - (l_last(X) + l_blind(X))). This // will never underflow because of the requirement of at least a degree // 3 circuit for the permutation argument. - assert!(pk.vk.cs.degree() >= 3); - let chunk_len = pk.vk.cs.degree() - 2; + assert!(pk.vk.cs_degree >= 3); + let chunk_len = pk.vk.cs_degree - 2; let blinding_factors = pk.vk.cs.blinding_factors(); // Each column gets its own delta power. diff --git a/halo2_proofs/src/plonk/permutation/verifier.rs b/halo2_proofs/src/plonk/permutation/verifier.rs index 7f7b926721..e2618d03b6 100644 --- a/halo2_proofs/src/plonk/permutation/verifier.rs +++ b/halo2_proofs/src/plonk/permutation/verifier.rs @@ -39,7 +39,7 @@ impl Argument { vk: &plonk::VerifyingKey, transcript: &mut T, ) -> Result, Error> { - let chunk_len = vk.cs.degree() - 2; + let chunk_len = vk.cs_degree - 2; let permutation_product_commitments = self .columns @@ -114,7 +114,7 @@ impl Evaluated { gamma: ChallengeGamma, x: ChallengeX, ) -> impl Iterator + 'a { - let chunk_len = vk.cs.degree() - 2; + let chunk_len = vk.cs_degree - 2; iter::empty() // Enforce only for the first set. // l_0(X) * (1 - z_0(X)) = 0 diff --git a/halo2_proofs/src/plonk/prover.rs b/halo2_proofs/src/plonk/prover.rs index bf96aa3df4..0983430295 100644 --- a/halo2_proofs/src/plonk/prover.rs +++ b/halo2_proofs/src/plonk/prover.rs @@ -16,22 +16,18 @@ use super::{ ChallengeY, Error, ProvingKey, }; use crate::{ - arithmetic::{eval_polynomial, BaseExt, CurveAffine, FieldExt}, - plonk::Assigned, -}; -use crate::{ - plonk::Expression, + arithmetic::{eval_polynomial, CurveAffine, FieldExt}, + circuit::Value, + plonk::{Assigned, Expression}, poly::{ self, + batch_invert_assigned, commitment::{Blind, Params}, multiopen::{self, ProverQuery}, + transcript::{EncodedChallenge, TranscriptWrite}, Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation, }, }; -use crate::{ - poly::batch_invert_assigned, - transcript::{EncodedChallenge, TranscriptWrite}, -}; /// This creates a proof for the provided `circuit` when given the public /// parameters `params` and the proving key [`ProvingKey`] that was @@ -176,7 +172,7 @@ pub fn create_proof< &self, column: Column, row: usize, - ) -> Result, Error> { + ) -> Result, Error> { if !self.usable_rows.contains(&row) { return Err(Error::not_enough_rows_available(self.k)); } @@ -184,7 +180,7 @@ pub fn create_proof< self.instances .get(column.index()) .and_then(|column| column.get(row)) - .map(|v| Some(*v)) + .map(|v| Value::known(*v)) .ok_or(Error::BoundsFailure) } @@ -196,7 +192,7 @@ pub fn create_proof< to: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> Value, VR: Into>, A: FnOnce() -> AR, AR: Into, @@ -209,7 +205,7 @@ pub fn create_proof< .advice .get_mut(column.index()) .and_then(|v| v.get_mut(row)) - .ok_or(Error::BoundsFailure)? = to()?.into(); + .ok_or(Error::BoundsFailure)? = to().into_field().assign()?; Ok(()) } @@ -222,7 +218,7 @@ pub fn create_proof< _: V, ) -> Result<(), Error> where - V: FnOnce() -> Result, + V: FnOnce() -> Value, VR: Into>, A: FnOnce() -> AR, AR: Into, @@ -248,7 +244,7 @@ pub fn create_proof< &mut self, _: Column, _: usize, - _: Option>, + _: Value>, ) -> Result<(), Error> { Ok(()) } diff --git a/halo2_proofs/src/plonk/verifier.rs b/halo2_proofs/src/plonk/verifier.rs index b38a8a0db9..50111021a0 100644 --- a/halo2_proofs/src/plonk/verifier.rs +++ b/halo2_proofs/src/plonk/verifier.rs @@ -19,6 +19,11 @@ use crate::poly::{ }; use crate::transcript::{read_n_points, read_n_scalars, EncodedChallenge, TranscriptRead}; +#[cfg(feature = "batch")] +mod batch; +#[cfg(feature = "batch")] +pub use batch::BatchVerifier; + /// Trait representing a strategy for verifying Halo 2 proofs. pub trait VerificationStrategy { /// The output type of this verification strategy after processing a proof. @@ -275,9 +280,9 @@ pub fn verify_proof< poly.evaluate( &|scalar| scalar, &|_| panic!("virtual selectors are removed during optimization"), - &|index, _, _| fixed_evals[index], - &|index, _, _| advice_evals[index], - &|index, _, _| instance_evals[index], + &|query| fixed_evals[query.index], + &|query| advice_evals[query.index], + &|query| instance_evals[query.index], &|a| -a, &|a, b| a + &b, &|a, b| a * &b, diff --git a/halo2_proofs/src/plonk/verifier/batch.rs b/halo2_proofs/src/plonk/verifier/batch.rs new file mode 100644 index 0000000000..b0375ad245 --- /dev/null +++ b/halo2_proofs/src/plonk/verifier/batch.rs @@ -0,0 +1,120 @@ +use std::{io, marker::PhantomData}; + +use group::ff::Field; +use pasta_curves::arithmetic::CurveAffine; +use rand_core::{OsRng, RngCore}; +use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; + +use super::{verify_proof, VerificationStrategy}; +use crate::{ + multicore, + plonk::{Error, VerifyingKey}, + poly::commitment::{Guard, Params, MSM}, + transcript::{Blake2bRead, EncodedChallenge, TranscriptRead}, +}; + +/// A proof verification strategy that returns the proof's MSM. +/// +/// `BatchVerifier` handles the accumulation of the MSMs for the batched proofs. +#[derive(Debug)] +struct BatchStrategy<'params, C: CurveAffine> { + msm: MSM<'params, C>, +} + +impl<'params, C: CurveAffine> BatchStrategy<'params, C> { + fn new(params: &'params Params) -> Self { + BatchStrategy { + msm: MSM::new(params), + } + } +} + +impl<'params, C: CurveAffine> VerificationStrategy<'params, C> for BatchStrategy<'params, C> { + type Output = MSM<'params, C>; + + fn process>( + self, + f: impl FnOnce(MSM<'params, C>) -> Result, Error>, + ) -> Result { + let guard = f(self.msm)?; + Ok(guard.use_challenges()) + } +} + +#[derive(Debug)] +struct BatchItem { + instances: Vec>>, + proof: Vec, +} + +/// A verifier that checks multiple proofs in a batch. **This requires the +/// `batch` crate feature to be enabled.** +#[derive(Debug, Default)] +pub struct BatchVerifier { + items: Vec>, +} + +impl BatchVerifier { + /// Constructs a new batch verifier. + pub fn new() -> Self { + Self { items: vec![] } + } + + /// Adds a proof to the batch. + pub fn add_proof(&mut self, instances: Vec>>, proof: Vec) { + self.items.push(BatchItem { instances, proof }) + } + + /// Finalizes the batch and checks its validity. + /// + /// Returns `false` if *some* proof was invalid. If the caller needs to identify + /// specific failing proofs, it must re-process the proofs separately. + /// + /// This uses [`OsRng`] internally instead of taking an `R: RngCore` argument, because + /// the internal parallelization requires access to a RNG that is guaranteed to not + /// clone its internal state when shared between threads. + pub fn finalize(self, params: &Params, vk: &VerifyingKey) -> bool { + fn accumulate_msm<'params, C: CurveAffine>( + mut acc: MSM<'params, C>, + msm: MSM<'params, C>, + ) -> MSM<'params, C> { + // Scale the MSM by a random factor to ensure that if the existing MSM has + // `is_zero() == false` then this argument won't be able to interfere with it + // to make it true, with high probability. + acc.scale(C::Scalar::random(OsRng)); + + acc.add_msm(&msm); + acc + } + + let final_msm = self + .items + .into_par_iter() + .enumerate() + .map(|(i, item)| { + let instances: Vec> = item + .instances + .iter() + .map(|i| i.iter().map(|c| &c[..]).collect()) + .collect(); + let instances: Vec<_> = instances.iter().map(|i| &i[..]).collect(); + + let strategy = BatchStrategy::new(params); + let mut transcript = Blake2bRead::init(&item.proof[..]); + verify_proof(params, vk, strategy, &instances, &mut transcript).map_err(|e| { + tracing::debug!("Batch item {} failed verification: {}", i, e); + e + }) + }) + .try_fold( + || params.empty_msm(), + |msm, res| res.map(|proof_msm| accumulate_msm(msm, proof_msm)), + ) + .try_reduce(|| params.empty_msm(), |a, b| Ok(accumulate_msm(a, b))); + + match final_msm { + Ok(msm) => msm.eval(), + Err(_) => false, + } + } +} diff --git a/halo2_proofs/src/poly.rs b/halo2_proofs/src/poly.rs index 2d913e25bf..b580234575 100644 --- a/halo2_proofs/src/poly.rs +++ b/halo2_proofs/src/poly.rs @@ -209,7 +209,7 @@ impl<'a, F: Field, B: Basis> Sub<&'a Polynomial> for Polynomial { } } -impl<'a, F: Field> Polynomial { +impl Polynomial { /// Rotates the values in a Lagrange basis polynomial by `Rotation` pub fn rotate(&self, rotation: Rotation) -> Polynomial { let mut values = self.values.clone(); @@ -225,17 +225,8 @@ impl<'a, F: Field> Polynomial { } } -impl<'a, F: Field, B: Basis> Sub for &'a Polynomial { - type Output = Polynomial; - - fn sub(self, rhs: F) -> Polynomial { - let mut res = self.clone(); - res.values[0] -= rhs; - res - } -} -impl<'a, F: Field, B: Basis> Mul for Polynomial { +impl Mul for Polynomial { type Output = Polynomial; fn mul(mut self, rhs: F) -> Polynomial { diff --git a/halo2_proofs/src/poly/commitment.rs b/halo2_proofs/src/poly/commitment.rs index 388a76e1f8..a1d4c6693f 100644 --- a/halo2_proofs/src/poly/commitment.rs +++ b/halo2_proofs/src/poly/commitment.rs @@ -17,8 +17,8 @@ use std::ops::{Add, AddAssign, Mul, MulAssign}; use std::io; -/// These are the prover parameters for the polynomial commitment scheme. -#[derive(Debug)] +/// These are the public parameters for the polynomial commitment scheme. +#[derive(Clone, Debug)] pub struct Params { /// This is a common term used for deriving the amount of field elements `n = 1 << k`. /// Also known as the domain size. diff --git a/halo2_proofs/src/poly/domain.rs b/halo2_proofs/src/poly/domain.rs index 5204ebef26..8e23047461 100644 --- a/halo2_proofs/src/poly/domain.rs +++ b/halo2_proofs/src/poly/domain.rs @@ -15,7 +15,7 @@ use std::marker::PhantomData; /// This structure contains precomputed constants and other details needed for /// performing operations on an evaluation domain of size $2^k$ and an extended /// domain of size $2^{k} * j$ with $j \neq 0$. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct EvaluationDomain { n: u64, k: u32, @@ -400,7 +400,7 @@ impl EvaluationDomain { } else { point *= &self .get_omega_inv() - .pow_vartime(&[(rotation.0 as i64).abs() as u64]); + .pow_vartime(&[(rotation.0 as i64).unsigned_abs()]); } point } diff --git a/halo2_proofs/tests/plonk_api.rs b/halo2_proofs/tests/plonk_api.rs index 534005c9fa..b6fe16b2a2 100644 --- a/halo2_proofs/tests/plonk_api.rs +++ b/halo2_proofs/tests/plonk_api.rs @@ -2,8 +2,8 @@ #![allow(clippy::op_ref)] use assert_matches::assert_matches; -use halo2_proofs::arithmetic::FieldExt; -use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner}; +use halo2_proofs::arithmetic::{CurveAffine, FieldExt}; +use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value}; use halo2_proofs::dev::MockProver; use halo2_proofs::plonk::{ create_proof, keygen_pk, keygen_vk, verify_proof, Advice, BatchVerifier, Circuit, Column, @@ -58,18 +58,18 @@ fn plonk_api() { f: F, ) -> Result<(Cell, Cell, Cell), Error> where - F: FnMut() -> Result<(FF, FF, FF), Error>; + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; fn raw_add( &self, layouter: &mut impl Layouter, f: F, ) -> Result<(Cell, Cell, Cell), Error> where - F: FnMut() -> Result<(FF, FF, FF), Error>; + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>; fn copy(&self, layouter: &mut impl Layouter, a: Cell, b: Cell) -> Result<(), Error>; fn public_input(&self, layouter: &mut impl Layouter, f: F) -> Result where - F: FnMut() -> Result; + F: FnMut() -> Value; fn lookup_table( &self, layouter: &mut impl Layouter, @@ -79,7 +79,7 @@ fn plonk_api() { #[derive(Clone)] struct MyCircuit { - a: Option, + a: Value, lookup_table: Vec, } @@ -104,7 +104,7 @@ fn plonk_api() { mut f: F, ) -> Result<(Cell, Cell, Cell), Error> where - F: FnMut() -> Result<(FF, FF, FF), Error>, + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, { layouter.assign_region( || "raw_multiply", @@ -115,39 +115,44 @@ fn plonk_api() { self.config.a, 0, || { - value = Some(f()?); - Ok(value.ok_or(Error::Synthesis)?.0) + value = Some(f()); + value.unwrap().map(|v| v.0) }, )?; region.assign_advice( || "lhs^4", self.config.d, 0, - || Ok(value.ok_or(Error::Synthesis)?.0.square().square()), + || value.unwrap().map(|v| v.0).square().square(), )?; let rhs = region.assign_advice( || "rhs", self.config.b, 0, - || Ok(value.ok_or(Error::Synthesis)?.1), + || value.unwrap().map(|v| v.1), )?; region.assign_advice( || "rhs^4", self.config.e, 0, - || Ok(value.ok_or(Error::Synthesis)?.1.square().square()), + || value.unwrap().map(|v| v.1).square().square(), )?; let out = region.assign_advice( || "out", self.config.c, 0, - || Ok(value.ok_or(Error::Synthesis)?.2), + || value.unwrap().map(|v| v.2), )?; - region.assign_fixed(|| "a", self.config.sa, 0, || Ok(FF::zero()))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Ok(FF::zero()))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Ok(FF::one()))?; - region.assign_fixed(|| "a * b", self.config.sm, 0, || Ok(FF::one()))?; + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::zero()))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::zero()))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?; + region.assign_fixed( + || "a * b", + self.config.sm, + 0, + || Value::known(FF::one()), + )?; Ok((lhs.cell(), rhs.cell(), out.cell())) }, ) @@ -158,7 +163,7 @@ fn plonk_api() { mut f: F, ) -> Result<(Cell, Cell, Cell), Error> where - F: FnMut() -> Result<(FF, FF, FF), Error>, + F: FnMut() -> Value<(Assigned, Assigned, Assigned)>, { layouter.assign_region( || "raw_add", @@ -169,39 +174,44 @@ fn plonk_api() { self.config.a, 0, || { - value = Some(f()?); - Ok(value.ok_or(Error::Synthesis)?.0) + value = Some(f()); + value.unwrap().map(|v| v.0) }, )?; region.assign_advice( || "lhs^4", self.config.d, 0, - || Ok(value.ok_or(Error::Synthesis)?.0.square().square()), + || value.unwrap().map(|v| v.0).square().square(), )?; let rhs = region.assign_advice( || "rhs", self.config.b, 0, - || Ok(value.ok_or(Error::Synthesis)?.1), + || value.unwrap().map(|v| v.1), )?; region.assign_advice( || "rhs^4", self.config.e, 0, - || Ok(value.ok_or(Error::Synthesis)?.1.square().square()), + || value.unwrap().map(|v| v.1).square().square(), )?; let out = region.assign_advice( || "out", self.config.c, 0, - || Ok(value.ok_or(Error::Synthesis)?.2), + || value.unwrap().map(|v| v.2), )?; - region.assign_fixed(|| "a", self.config.sa, 0, || Ok(FF::one()))?; - region.assign_fixed(|| "b", self.config.sb, 0, || Ok(FF::one()))?; - region.assign_fixed(|| "c", self.config.sc, 0, || Ok(FF::one()))?; - region.assign_fixed(|| "a * b", self.config.sm, 0, || Ok(FF::zero()))?; + region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::one()))?; + region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::one()))?; + region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?; + region.assign_fixed( + || "a * b", + self.config.sm, + 0, + || Value::known(FF::zero()), + )?; Ok((lhs.cell(), rhs.cell(), out.cell())) }, ) @@ -222,13 +232,18 @@ fn plonk_api() { } fn public_input(&self, layouter: &mut impl Layouter, mut f: F) -> Result where - F: FnMut() -> Result, + F: FnMut() -> Value, { layouter.assign_region( || "public_input", |mut region| { let value = region.assign_advice(|| "value", self.config.a, 0, &mut f)?; - region.assign_fixed(|| "public", self.config.sp, 0, || Ok(FF::one()))?; + region.assign_fixed( + || "public", + self.config.sp, + 0, + || Value::known(FF::one()), + )?; Ok(value.cell()) }, @@ -243,7 +258,12 @@ fn plonk_api() { || "", |mut table| { for (index, &value) in values.iter().enumerate() { - table.assign_cell(|| "table col", self.config.sl, index, || Ok(value))?; + table.assign_cell( + || "table col", + self.config.sl, + index, + || Value::known(value), + )?; } Ok(()) }, @@ -258,7 +278,7 @@ fn plonk_api() { fn without_witnesses(&self) -> Self { Self { - a: None, + a: Value::unknown(), lookup_table: self.lookup_table.clone(), } } @@ -360,25 +380,20 @@ fn plonk_api() { ) -> Result<(), Error> { let cs = StandardPlonk::new(config); - let _ = cs.public_input(&mut layouter, || Ok(F::one() + F::one()))?; + let _ = cs.public_input(&mut layouter, || Value::known(F::one() + F::one()))?; for _ in 0..10 { - let mut a_squared = None; + let a: Value> = self.a.into(); + let mut a_squared = Value::unknown(); let (a0, _, c0) = cs.raw_multiply(&mut layouter, || { - a_squared = self.a.map(|a| a.square()); - Ok(( - self.a.ok_or(Error::Synthesis)?, - self.a.ok_or(Error::Synthesis)?, - a_squared.ok_or(Error::Synthesis)?, - )) + a_squared = a.square(); + a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared)) })?; let (a1, b1, _) = cs.raw_add(&mut layouter, || { - let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2)); - Ok(( - self.a.ok_or(Error::Synthesis)?, - a_squared.ok_or(Error::Synthesis)?, - fin.ok_or(Error::Synthesis)?, - )) + let fin = a_squared + a; + a.zip(a_squared) + .zip(fin) + .map(|((a, a_squared), fin)| (a, a_squared, fin)) })?; cs.copy(&mut layouter, a0, a1)?; cs.copy(&mut layouter, b1, c0)?; @@ -395,12 +410,12 @@ fn plonk_api() { let lookup_table = vec![instance, a, a, Fp::zero()]; let empty_circuit: MyCircuit = MyCircuit { - a: None, + a: Value::unknown(), lookup_table: lookup_table.clone(), }; let circuit: MyCircuit = MyCircuit { - a: Some(a), + a: Value::known(a), lookup_table, }; @@ -438,6 +453,39 @@ fn plonk_api() { }; assert_eq!(prover.verify(), Ok(())); + if std::env::var_os("HALO2_PLONK_TEST_GENERATE_NEW_PROOF").is_some() { + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + // Create a proof + create_proof( + ¶ms, + &pk, + &[circuit.clone(), circuit.clone()], + &[&[&[instance]], &[&[instance]]], + OsRng, + &mut transcript, + ) + .expect("proof generation should not fail"); + let proof: Vec = transcript.finalize(); + + std::fs::write("plonk_api_proof.bin", &proof[..]) + .expect("should succeed to write new proof"); + } + + { + // Check that a hardcoded proof is satisfied + let proof = include_bytes!("plonk_api_proof.bin"); + let strategy = SingleVerifier::new(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + assert!(verify_proof( + ¶ms, + pk.get_vk(), + strategy, + &[&[&pubinputs[..]], &[&pubinputs[..]]], + &mut transcript, + ) + .is_ok()); + } + for _ in 0..10 { let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); // Create a proof @@ -471,42 +519,424 @@ fn plonk_api() { // { - let strategy = BatchVerifier::new(¶ms_verifier, OsRng); + let mut batch = BatchVerifier::new(); // First proof. - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - let strategy = verify_proof( - ¶ms_verifier, - pk.get_vk(), - strategy, - &[&[&pubinputs[..]], &[&pubinputs[..]]], - &mut transcript, - ) - .unwrap(); - - // Write and then read the verification key in between (to check round-trip - // serialization). - // TODO: Figure out whether https://github.com/zcash/halo2/issues/449 should - // be caught by this, or if it is caused by downstream changes to halo2. - let mut vk_buffer = vec![]; - pk.get_vk().write(&mut vk_buffer).unwrap(); - let vk = - VerifyingKey::::read::<_, MyCircuit>(&mut &vk_buffer[..], ¶ms) - .unwrap(); + batch.add_proof( + vec![vec![pubinputs.clone()], vec![pubinputs.clone()]], + proof.clone(), + ); // "Second" proof (just the first proof again). - let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); - let strategy = verify_proof( - ¶ms_verifier, - &vk, - strategy, - &[&[&pubinputs[..]], &[&pubinputs[..]]], - &mut transcript, - ) - .unwrap(); + batch.add_proof( + vec![vec![pubinputs.clone()], vec![pubinputs.clone()]], + proof, + ); // Check the batch. - assert!(strategy.finalize()); + assert!(batch.finalize(¶ms, pk.get_vk())); } } + + // Check that the verification key has not changed unexpectedly + { + //panic!("{:#?}", pk.get_vk().pinned()); + assert_eq!( + format!("{:#?}", pk.get_vk().pinned()), + r#####"PinnedVerificationKey { + base_modulus: "0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001", + scalar_modulus: "0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001", + domain: PinnedEvaluationDomain { + k: 5, + extended_k: 7, + omega: 0x0cc3380dc616f2e1daf29ad1560833ed3baea3393eceb7bc8fa36376929b78cc, + }, + cs: PinnedConstraintSystem { + num_fixed_columns: 7, + num_advice_columns: 5, + num_instance_columns: 1, + num_selectors: 0, + gates: [ + Sum( + Sum( + Sum( + Sum( + Product( + Advice { + query_index: 0, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 2, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Product( + Advice { + query_index: 1, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 3, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + ), + ), + Product( + Product( + Advice { + query_index: 0, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Advice { + query_index: 1, + column_index: 2, + rotation: Rotation( + 0, + ), + }, + ), + Fixed { + query_index: 5, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ), + ), + Negated( + Product( + Advice { + query_index: 2, + column_index: 3, + rotation: Rotation( + 0, + ), + }, + Fixed { + query_index: 4, + column_index: 4, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + Product( + Fixed { + query_index: 1, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + Product( + Advice { + query_index: 3, + column_index: 4, + rotation: Rotation( + 1, + ), + }, + Advice { + query_index: 4, + column_index: 0, + rotation: Rotation( + -1, + ), + }, + ), + ), + ), + Product( + Fixed { + query_index: 6, + column_index: 5, + rotation: Rotation( + 0, + ), + }, + Sum( + Advice { + query_index: 0, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + Negated( + Instance { + query_index: 0, + column_index: 0, + rotation: Rotation( + 0, + ), + }, + ), + ), + ), + ], + advice_queries: [ + ( + Column { + index: 1, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 2, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 3, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 4, + column_type: Advice, + }, + Rotation( + 1, + ), + ), + ( + Column { + index: 0, + column_type: Advice, + }, + Rotation( + -1, + ), + ), + ( + Column { + index: 0, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 4, + column_type: Advice, + }, + Rotation( + 0, + ), + ), + ], + instance_queries: [ + ( + Column { + index: 0, + column_type: Instance, + }, + Rotation( + 0, + ), + ), + ], + fixed_queries: [ + ( + Column { + index: 6, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 0, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 2, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 3, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 4, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 1, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ( + Column { + index: 5, + column_type: Fixed, + }, + Rotation( + 0, + ), + ), + ], + permutation: Argument { + columns: [ + Column { + index: 1, + column_type: Advice, + }, + Column { + index: 2, + column_type: Advice, + }, + Column { + index: 3, + column_type: Advice, + }, + Column { + index: 0, + column_type: Fixed, + }, + Column { + index: 0, + column_type: Advice, + }, + Column { + index: 4, + column_type: Advice, + }, + Column { + index: 0, + column_type: Instance, + }, + Column { + index: 1, + column_type: Fixed, + }, + Column { + index: 2, + column_type: Fixed, + }, + Column { + index: 3, + column_type: Fixed, + }, + Column { + index: 4, + column_type: Fixed, + }, + Column { + index: 5, + column_type: Fixed, + }, + ], + }, + lookups: [ + Argument { + input_expressions: [ + Advice { + query_index: 0, + column_index: 1, + rotation: Rotation( + 0, + ), + }, + ], + table_expressions: [ + Fixed { + query_index: 0, + column_index: 6, + rotation: Rotation( + 0, + ), + }, + ], + }, + ], + constants: [], + minimum_degree: None, + }, + fixed_commitments: [ + (0x2bbc94ef7b22aebef24f9a4b0cc1831882548b605171366017d45c3e6fd92075, 0x082b801a6e176239943bfb759fb02138f47a5c8cc4aa7fa0af559fde4e3abd97), + (0x2bf5082b105b2156ed0e9c5b8e42bf2a240b058f74a464d080e9585274dd1e84, 0x222ad83cee7777e7a160585e212140e5e770dd8d1df788d869b5ee483a5864fb), + (0x374a656456a0aae7429b23336f825752b575dd5a44290ff614946ee59d6a20c0, 0x054491e187e6e3460e7601fb54ae10836d34d420026f96316f0c5c62f86db9b8), + (0x374a656456a0aae7429b23336f825752b575dd5a44290ff614946ee59d6a20c0, 0x054491e187e6e3460e7601fb54ae10836d34d420026f96316f0c5c62f86db9b8), + (0x02e62cd68370b13711139a08cbcdd889e800a272b9ea10acc90880fff9d89199, 0x1a96c468cb0ce77065d3a58f1e55fea9b72d15e44c01bba1e110bd0cbc6e9bc6), + (0x224ef42758215157d3ee48fb8d769da5bddd35e5929a90a4a89736f5c4b5ae9b, 0x11bc3a1e08eb320cde764f1492ecef956d71e996e2165f7a9a30ad2febb511c1), + (0x2d5415bf917fcac32bfb705f8ca35cb12d9bad52aa33ccca747350f9235d3a18, 0x2b2921f815fad504052512743963ef20ed5b401d20627793b006413e73fe4dd4), + ], + permutation: VerifyingKey { + commitments: [ + (0x1347b4b385837977a96b87f199c6a9a81520015539d1e8fa79429bb4ca229a00, 0x2168e404cabef513654d6ff516cde73f0ba87e3dc84e4b940ed675b5f66f3884), + (0x0e6d69cd2455ec43be640f6397ed65c9e51b1d8c0fd2216339314ff37ade122a, 0x222ed6dc8cfc9ea26dcc10b9d4add791ada60f2b5a63ee1e4635f88aa0c96654), + (0x13c447846f48c41a5e0675ccf88ebc0cdef2c96c51446d037acb866d24255785, 0x1f0b5414fc5e8219dbfab996eed6129d831488b2386a8b1a63663938903bd63a), + (0x1aae6470aa662b8fda003894ddef5fedd03af318b3231683039d2fac9cab05b9, 0x08832d91ae69e99cd07d096c7a4a284a69e6a16227cbb07932a0cdc56914f3a6), + (0x0850521b0f8ac7dd0550fe3e25c840837076e9635067ed623b81d5cbac5944d9, 0x0c25d65d1038d0a92c72e5fccd96c1caf07801c3c8233290bb292e0c38c256fa), + (0x12febcf696badd970750eabf75dd3ced4c2f54f93519bcee23849025177d2014, 0x0a05ab3cd42c9fbcc1bbfcf9269951640cc9920761c87cf8e211ba73c8d9f90f), + (0x053904bdde8cfead3b517bb4f6ded3e699f8b94ca6156a9dd2f92a2a05a7ec5a, 0x16753ff97c0d82ff586bb7a07bf7f27a92df90b3617fa5e75d4f55c3b0ef8711), + (0x3804548f6816452747a5b542fa5656353fc989db40d69e9e27d6f973b5deebb0, 0x389a44d5037866dd83993af75831a5f90a18ad5244255aa5bd2c922cc5853055), + (0x003a9f9ca71c7c0b832c802220915f6fc8d840162bdde6b0ea05d25fb95559e3, 0x091247ca19d6b73887cd7f68908cbf0db0b47459b7c82276bbdb8a1c937e2438), + (0x3eaa38689d9e391c8a8fafab9568f20c45816321d38f309d4cc37f4b1601af72, 0x247f8270a462ea88450221a56aa6b55d2bc352b80b03501e99ea983251ceea13), + (0x394437571f9de32dccdc546fd4737772d8d92593c85438aa3473243997d5acc8, 0x14924ec6e3174f1fab7f0ce7070c22f04bbd0a0ecebdfc5c94be857f25493e95), + (0x3d907e0591343bd285c2c846f3e871a6ac70d80ec29e9500b8cb57f544e60202, 0x1034e48df35830244cabea076be8a16d67d7896e27c6ac22b285d017105da9c3), + ], + }, +}"##### + ); + } } diff --git a/halo2_proofs/tests/plonk_api_proof.bin b/halo2_proofs/tests/plonk_api_proof.bin new file mode 100644 index 0000000000..ecc92f0d41 Binary files /dev/null and b/halo2_proofs/tests/plonk_api_proof.bin differ diff --git a/rust-toolchain b/rust-toolchain index dabc0d9f12..2e625890df 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2021-11-17 \ No newline at end of file +nightly-2021-11-17