Skip to content

Commit

Permalink
Compute KZG proof directly from evaluation form
Browse files Browse the repository at this point in the history
Previously we were using IFFT to go from evaluation form to coefficient form before computing the KZG proof in the
traditional way.

We can instead do the KZG proof division directly in evaluation form by using point-by-point division:
   https://dankradfeist.de/ethereum/2021/06/18/pcs-multiproofs.html#dividing-when-one-of-the-points-is-zero

Two fine points:
- We need to check that the denominator of the division is not zero. It's very unlikely because `z` comes out of
Fiat-Shamir, but we still add an assert to make sure.

- We need to shift our main polynomial by `p(z)` as in the traditional KZG proof formula. We can avoid this
in coefficient form because with long division we can extract the quotient polynomial and ignore the remainder.
However that's not possible when doing point-by-point division in evaluation form and hence we need to ensure
that there is no remainder by shifting by `p(z)`.
  • Loading branch information
asn-d6 committed Jul 8, 2022
1 parent 247850a commit 294f050
Showing 1 changed file with 14 additions and 48 deletions.
62 changes: 14 additions & 48 deletions specs/eip4844/polynomial-commitments.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,18 +150,24 @@ def verify_kzg_proof(polynomial_kzg: KZGCommitment,
#### `compute_kzg_proof`

```python
def compute_kzg_proof(polynomial: Sequence[BLSFieldElement], x: BLSFieldElement) -> KZGProof:
def compute_kzg_proof(polynomial: Sequence[BLSFieldElement], z: BLSFieldElement) -> KZGProof:
"""Compute KZG proof at point `z` with `polynomial` being in evaluation form"""

# To avoid SSZ overflow/underflow, convert element into int
polynomial = [int(i) for i in polynomial]
z = int(z)

# Shift our polynomial first (in evaluation form we can't handle the division remainder)
y = evaluate_polynomial_in_evaluation_form(polynomial, z)
polynomial_shifted = [(p - int(y)) % BLS_MODULUS for p in polynomial]

# Convert `polynomial` to coefficient form
assert pow(ROOTS_OF_UNITY[1], len(polynomial), BLS_MODULUS) == 1
fft_output = fft(polynomial, ROOTS_OF_UNITY)
inv_length = pow(len(polynomial), BLS_MODULUS - 2, BLS_MODULUS)
polynomial_in_coefficient_form = [fft_output[-i] * inv_length % BLS_MODULUS for i in range(len(fft_output))]
# Make sure we won't divide by zero during division
assert z not in ROOTS_OF_UNITY

This comment has been minimized.

Copy link
@dankrad

dankrad Jul 8, 2022

This check also needs to be added to the verification of the proof.

This comment has been minimized.

Copy link
@asn-d6

asn-d6 Jul 11, 2022

Author Owner

Addressed in ethereum@e989eaf by checking for div-by-zero in the barycentric evaluation.

During proof verification we check that: $p(s) - p(z) = q(s)(s - z)$.

In the above, we use $z$ in $s-z$ where it does not hurt us if $z$ is a root of unity.
However, when evaluating $p(z)$ using the barycentric formula, there is a danger for division by zero, and that's why I added that check.

An honest validator would never create a block/transaction where the evaluation point for the KZG proof is a root of unity (because of the assert in compute_kzg_proof() and also because it's prohibitively expensive to do so using Fiat-Shamir queries) but it might still be worth being defensive here.

This comment has been minimized.

Copy link
@dankrad

dankrad Jul 12, 2022

I see! Interesting but I would also suggest to add a comment where the function is called to clarify that it performs that check at the same time.

This comment has been minimized.

Copy link
@asn-d6

asn-d6 Jul 13, 2022

Author Owner

ACK! Did so at c8b8c1e .

denominator_poly = [(x - z) % BLS_MODULUS for x in ROOTS_OF_UNITY]

quotient_polynomial = div_polys(polynomial_in_coefficient_form, [-int(x), 1])
return KZGProof(lincomb(KZG_SETUP_G1[:len(quotient_polynomial)], quotient_polynomial))
# Calculate quotient polynomial by doing point-by-point division
quotient_polynomial = [div(a, b) for a, b in zip(polynomial_shifted, denominator_poly)]
return KZGProof(lincomb(KZG_SETUP_LAGRANGE[:len(quotient_polynomial)], quotient_polynomial))

This comment has been minimized.

Copy link
@dankrad

dankrad Jul 8, 2022

Remove [:len(quotient_polynomial)] as they should always be the same length and this would catch if they are not

This comment has been minimized.

Copy link
@asn-d6

asn-d6 Jul 11, 2022

Author Owner

Addressed in ethereum@f494e68

This comment has been minimized.

Copy link
@dankrad

dankrad Jul 13, 2022

That commit addresses it, but it doesn't seem to be in this branch? "This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository."

This comment has been minimized.

Copy link
@asn-d6

asn-d6 Jul 13, 2022

Author Owner

Yes I've noticed that... I think I've screwed up something here and github does not recognize the right branch for this set of changes.

The right branch was at https://github.com/asn-d6/consensus-specs/commits/eip4844-exe
Since your ACK it has been merged into the bigger PR ethereum#2901 with this commit: ethereum@57d12d8

I'm now in the process of rebasing ethereum#2901 into something more palatable.

```

### Polynomials
Expand All @@ -187,43 +193,3 @@ def evaluate_polynomial_in_evaluation_form(polynomial: Sequence[BLSFieldElement]
return result
```

#### `fft`

```python
def fft(vals, domain):
"""
FFT for ``BLSFieldElement``.
"""
if len(vals) == 1:
return vals
L = fft(vals[::2], domain[::2])
R = fft(vals[1::2], domain[::2])
result = [0] * len(vals)
for i, (x, y) in enumerate(zip(L, R)):
y_times_root = y * domain[i] % BLS_MODULUS
result[i] = x + y_times_root % BLS_MODULUS
result[i + len(L)] = x + (BLS_MODULUS - y_times_root) % BLS_MODULUS
return result
```

#### `div_polys`

```python
def div_polys(a: Sequence[int], b: Sequence[int]) -> Sequence[BLSFieldElement]:
"""
Long polynomial division for two polynomials in coefficient form.
"""
a = a.copy() # avoid side effects
result = []
a_position = len(a) - 1
b_position = len(b) - 1
diff = a_position - b_position
while diff >= 0:
quotient = div(a[a_position], b[b_position])
result.insert(0, quotient)
for i in range(b_position, -1, -1):
a[diff + i] -= b[i] * quotient
a_position -= 1
diff -= 1
return [x % BLS_MODULUS for x in result]
```

0 comments on commit 294f050

Please sign in to comment.