Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove redundant range check in rotation gadget #1201

Merged
merged 9 commits into from
Nov 17, 2023

Conversation

mitschabaude
Copy link
Collaborator

@mitschabaude mitschabaude commented Oct 27, 2023

This PR removes one range check used in Gadgets.rotate(), which was originally added as a conservative measure. What follows is a proof that the check is not needed for soundness.

The rotation gadget

The 64-bit rotation gate takes as inputs four variables: input $n$, rotated output $r$, shifted input $s$ and excess $x$. It also takes the limbs of a bound $b$. A coefficient of the gate is the number of bits to rotated, $R \in [0, 64]$.

The first check that the gate does is the defining equation of $s$ and $x$.

$$\tag{c1} n 2^R = s + x 2^{64} \mod{p} $$

This equation tells us the following: When we shift $n$ by $R$ bits to the left, the result is a shifted value $s < 2^{64}$, plus some excess bits $x$ which are shifted to bit positions higher than 64.

Like all constraints, $(c1)$ is an equation modulo the native field size $p$.

The rotated value $r$ is then simply defined by the following, second gate equation:

$$\tag{c2} r = s + x \mod{p} $$

This "moves" the excess bits $x$ to the other end which is what a rotation is supposed to do.

The excess $x$ is connected to the bound $b$ by the third gate equation:

$$\tag{c3} b = x + 2^{64} - 2^R \mod{p} $$

Finally, the gate constrains the bound limbs to represent $b$, and also range-checks the bound to 64 bits.
To finish the gate, a second row always has to be added which adds the same range-check on $s$. In summary, we have

$$\tag{c4} b \in [0, 2^{64}) $$

$$\tag{c5} s \in [0, 2^{64}) $$

Also, the rotation gadget assumes that the input is range-checked:

$$\tag{a} n \in [0, 2^{64}) $$

Soundness proof

Proposition. Equations $(c1-c5)$, together with the assumption on the input $(a)$, imply the following:

  • The defining equations $(c1)$ and $(c2)$ are true over the integers, not just modulo $p$
  • $x \in [0, 2^{64})$ and $r \in [0, 2^{64})$

Proof. The range check on $b$ means that $0 \le x + 2^{64} - 2^R < 2^{64}$. This by itself implies that $x$ belongs to one of two intervals:

$$ x \in [0, 2^R) \cup [p - 2^{64} + 2^R, p-1] $$

Let's call the interval on the right the "overflow case". We wish to rule out this case and show that $x \in [0, 2^R)$.

Let's assume we are in the overflow case. Then, $1 \le (p-x) < 2^{64}$. We can rearrange $(c1)$ to get

$$\tag{c1'} (p - x) 2^{64} = s - n 2^R \mod{p} $$

Since $(p-x)$ is multiplied by $2^{64}$, the LHS lies in the range $[2^{64}, 2^{128})$. We want to show that the residue of the right hand side modulo $p$ cannot fall within this range.

In fact, since $s, n \in [0, 2^{64})$, the RHS belongs to one of two intervals:

$$ s - n 2^R \in [0, 2^{64}) \cup [p - 2^{64 + R}, p) $$

The left interval (corresponding to the case $s > n 2^R$) clearly does not overlap with $[2^{64}, 2^{128})$. Since $R \le 64$, the right interval is contained in $[p - 2^{128}, p)$. Since in our case $p > 2^{129}$, this interval also does not overlap with the LHS.

Because the possible intervals of LHS and RHS modulo $p$ have no overlap, $(c1')$ and therefore $(c1)$ are not satisfied, which contradicts our assumptions. The "overflow case" can therefore be ruled out, and we conclude $x \in [0, 2^R)$.

With this information, both sides of equation $(c1)$ can be bounded to small values, so this equation holds over the integers.
In particular, $s$ is a multiple of $2^R$.

Since $s < 2^{64}$, being a multiple of $2^R$ implies that $s \le 2^{64} - 2^R$. Because we showed $x < 2^R$, we can deduce that

$$ r = s + x \in [0, 2^{64}) $$

and equation $(c2)$ holds over the integers. /qed

Conclusion

The soundness proof showed that we don't have to add an extra range check which constrains $x \in [0, 2^{64})$. In fact, this bound is already implied by the gadget and is redundant. This PR removes the extra check. The rotation gadget only needs 2 instead of 3 rows.

@mitschabaude mitschabaude marked this pull request as ready for review October 31, 2023 15:25
@mitschabaude mitschabaude requested a review from a team as a code owner October 31, 2023 15:25
@@ -1,7 +1,7 @@
import { ZkProgram } from '../proof_system.js';
import {
Spec,
equivalent,
equivalentProvable as equivalent,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated fix: Unit tests need equivalentProvable which tests implementations in a circuit

@Trivo25
Copy link
Member

Trivo25 commented Oct 31, 2023

Since this improvement came after the new release I think we should add a quick paragraph to the changelog

Copy link
Member

@querolita querolita left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great improvement

@@ -66,6 +66,7 @@ const GroupMock = {
const BitwiseMock = {
rot() {
let a = Provable.witness(Field, () => new Field(12));
Gadgets.rangeCheck64(a); // `rotate()` doesn't do this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good, for checking the input is 64 bits in a stand-alone test

@mitschabaude mitschabaude merged commit 6e30ce7 into main Nov 17, 2023
13 checks passed
@mitschabaude mitschabaude deleted the feature/remove-rot-excess-rc branch November 17, 2023 12:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants