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

Use bls.Scalar as the base class for BLSFieldElement #3907

Merged
merged 24 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/*/*.md) \
$(wildcard $(SPEC_DIR)/_features/*/*/*.md) \
$(wildcard $(SSZ_DIR)/*.md)

ALL_EXECUTABLE_SPEC_NAMES = phase0 altair bellatrix capella deneb electra whisk eip6800 eip7732
ALL_EXECUTABLE_SPEC_NAMES = phase0 altair bellatrix capella deneb electra whisk eip6800 eip7594 eip7732
# The parameters for commands. Use `foreach` to avoid listing specs again.
COVERAGE_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), --cov=eth2spec.$S.$(TEST_PRESET_TYPE))
PYLINT_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), ./eth2spec/$S)
Expand Down
3 changes: 2 additions & 1 deletion pysetup/spec_builders/eip7594.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ def imports(cls, preset_name: str):
return f'''
from eth2spec.deneb import {preset_name} as deneb
'''


@classmethod
def sundry_functions(cls) -> str:
return """
def retrieve_column_sidecars(beacon_block_root: Root) -> Sequence[DataColumnSidecar]:
# pylint: disable=unused-argument
return []
"""

Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def _update_constant_vars_with_kzg_setups(constant_vars, preset_name):
constant_vars['KZG_SETUP_G1_MONOMIAL'] = VariableDefinition(constant_vars['KZG_SETUP_G1_MONOMIAL'].value, str(kzg_setups[0]), comment, None)
constant_vars['KZG_SETUP_G1_LAGRANGE'] = VariableDefinition(constant_vars['KZG_SETUP_G1_LAGRANGE'].value, str(kzg_setups[1]), comment, None)
constant_vars['KZG_SETUP_G2_MONOMIAL'] = VariableDefinition(constant_vars['KZG_SETUP_G2_MONOMIAL'].value, str(kzg_setups[2]), comment, None)


def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], preset_name=str) -> SpecObject:
functions: Dict[str, str] = {}
Expand Down Expand Up @@ -557,7 +557,7 @@ def run(self):
RUAMEL_YAML_VERSION,
"lru-dict==1.2.0",
MARKO_VERSION,
"py_arkworks_bls12381==0.3.4",
"py_arkworks_bls12381==0.3.5",
"curdleproofs==0.1.1",
]
)
163 changes: 79 additions & 84 deletions specs/_features/eip7594/polynomial-commitments-sampling.md

Large diffs are not rendered by default.

138 changes: 49 additions & 89 deletions specs/deneb/polynomial-commitments.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
- [`bytes_to_kzg_proof`](#bytes_to_kzg_proof)
- [`blob_to_polynomial`](#blob_to_polynomial)
- [`compute_challenge`](#compute_challenge)
- [`bls_modular_inverse`](#bls_modular_inverse)
- [`div`](#div)
- [`g1_lincomb`](#g1_lincomb)
- [`compute_powers`](#compute_powers)
- [`compute_roots_of_unity`](#compute_roots_of_unity)
Expand Down Expand Up @@ -63,10 +61,8 @@ Public functions MUST accept raw bytes as input and perform the required cryptog
| - | - | - |
| `G1Point` | `Bytes48` | |
| `G2Point` | `Bytes96` | |
| `BLSFieldElement` | `uint256` | Validation: `x < BLS_MODULUS` |
| `KZGCommitment` | `Bytes48` | Validation: Perform [BLS standard's](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-2.5) "KeyValidate" check but do allow the identity point |
| `KZGProof` | `Bytes48` | Same as for `KZGCommitment` |
| `Polynomial` | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB]` | A polynomial in evaluation form |
| `Blob` | `ByteVector[BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB]` | A basic data blob |

## Constants
Expand Down Expand Up @@ -162,33 +158,33 @@ def multi_exp(points: Sequence[TPoint],
#### `hash_to_bls_field`

```python
def hash_to_bls_field(data: bytes) -> BLSFieldElement:
def hash_to_bls_field(data: bytes) -> bls.Scalar:
jtraglia marked this conversation as resolved.
Show resolved Hide resolved
"""
Hash ``data`` and convert the output to a BLS scalar field element.
The output is not uniform over the BLS field.
"""
hashed_data = hash(data)
return BLSFieldElement(int.from_bytes(hashed_data, KZG_ENDIANNESS) % BLS_MODULUS)
return bls.Scalar(int.from_bytes(hashed_data, KZG_ENDIANNESS) % BLS_MODULUS)
```

#### `bytes_to_bls_field`

```python
def bytes_to_bls_field(b: Bytes32) -> BLSFieldElement:
def bytes_to_bls_field(b: Bytes32) -> bls.Scalar:
"""
Convert untrusted bytes to a trusted and validated BLS scalar field element.
This function does not accept inputs greater than the BLS modulus.
"""
field_element = int.from_bytes(b, KZG_ENDIANNESS)
assert field_element < BLS_MODULUS
return BLSFieldElement(field_element)
return bls.Scalar(field_element)
```

#### `bls_field_to_bytes`

```python
def bls_field_to_bytes(x: BLSFieldElement) -> Bytes32:
return int.to_bytes(x % BLS_MODULUS, 32, KZG_ENDIANNESS)
def bls_field_to_bytes(x: bls.Scalar) -> Bytes32:
return int.to_bytes(int(x), 32, KZG_ENDIANNESS)
```

#### `validate_kzg_g1`
Expand Down Expand Up @@ -229,22 +225,21 @@ def bytes_to_kzg_proof(b: Bytes48) -> KZGProof:
#### `blob_to_polynomial`

```python
def blob_to_polynomial(blob: Blob) -> Polynomial:
def blob_to_polynomial(blob: Blob) -> Sequence[bls.Scalar]:
"""
Convert a blob to list of BLS field scalars.
"""
polynomial = Polynomial()
polynomial = []
for i in range(FIELD_ELEMENTS_PER_BLOB):
value = bytes_to_bls_field(blob[i * BYTES_PER_FIELD_ELEMENT: (i + 1) * BYTES_PER_FIELD_ELEMENT])
polynomial[i] = value
polynomial.append(value)
return polynomial
```

#### `compute_challenge`

```python
def compute_challenge(blob: Blob,
commitment: KZGCommitment) -> BLSFieldElement:
def compute_challenge(blob: Blob, commitment: KZGCommitment) -> bls.Scalar:
"""
Return the Fiat-Shamir challenge required by the rest of the protocol.
"""
Expand All @@ -260,32 +255,10 @@ def compute_challenge(blob: Blob,
return hash_to_bls_field(data)
```

#### `bls_modular_inverse`

```python
def bls_modular_inverse(x: BLSFieldElement) -> BLSFieldElement:
"""
Compute the modular inverse of x (for x != 0)
i.e. return y such that x * y % BLS_MODULUS == 1
"""
assert (int(x) % BLS_MODULUS) != 0
return BLSFieldElement(pow(x, -1, BLS_MODULUS))
```

#### `div`

```python
def div(x: BLSFieldElement, y: BLSFieldElement) -> BLSFieldElement:
"""
Divide two field elements: ``x`` by `y``.
"""
return BLSFieldElement((int(x) * int(bls_modular_inverse(y))) % BLS_MODULUS)
```

#### `g1_lincomb`

```python
def g1_lincomb(points: Sequence[KZGCommitment], scalars: Sequence[BLSFieldElement]) -> KZGCommitment:
def g1_lincomb(points: Sequence[KZGCommitment], scalars: Sequence[bls.Scalar]) -> KZGCommitment:
"""
BLS multiscalar multiplication in G1. This can be naively implemented using double-and-add.
"""
Expand All @@ -305,27 +278,27 @@ def g1_lincomb(points: Sequence[KZGCommitment], scalars: Sequence[BLSFieldElemen
#### `compute_powers`

```python
def compute_powers(x: BLSFieldElement, n: uint64) -> Sequence[BLSFieldElement]:
def compute_powers(x: bls.Scalar, n: uint64) -> Sequence[bls.Scalar]:
"""
Return ``x`` to power of [0, n-1], if n > 0. When n==0, an empty array is returned.
"""
current_power = 1
current_power = bls.Scalar(1)
powers = []
for _ in range(n):
powers.append(BLSFieldElement(current_power))
current_power = current_power * int(x) % BLS_MODULUS
powers.append(current_power)
current_power = current_power * x
return powers
```

#### `compute_roots_of_unity`

```python
def compute_roots_of_unity(order: uint64) -> Sequence[BLSFieldElement]:
def compute_roots_of_unity(order: uint64) -> Sequence[bls.Scalar]:
"""
Return roots of unity of ``order``.
"""
assert (BLS_MODULUS - 1) % int(order) == 0
root_of_unity = BLSFieldElement(pow(PRIMITIVE_ROOT_OF_UNITY, (BLS_MODULUS - 1) // int(order), BLS_MODULUS))
root_of_unity = bls.Scalar(pow(PRIMITIVE_ROOT_OF_UNITY, (BLS_MODULUS - 1) // int(order), BLS_MODULUS))
return compute_powers(root_of_unity, order)
```

Expand All @@ -334,8 +307,7 @@ def compute_roots_of_unity(order: uint64) -> Sequence[BLSFieldElement]:
#### `evaluate_polynomial_in_evaluation_form`

```python
def evaluate_polynomial_in_evaluation_form(polynomial: Polynomial,
z: BLSFieldElement) -> BLSFieldElement:
def evaluate_polynomial_in_evaluation_form(polynomial: Sequence[bls.Scalar], z: bls.Scalar) -> bls.Scalar:
"""
Evaluate a polynomial (in evaluation form) at an arbitrary point ``z``.
- When ``z`` is in the domain, the evaluation can be found by indexing the polynomial at the
Expand All @@ -345,22 +317,23 @@ def evaluate_polynomial_in_evaluation_form(polynomial: Polynomial,
"""
width = len(polynomial)
assert width == FIELD_ELEMENTS_PER_BLOB
inverse_width = bls_modular_inverse(BLSFieldElement(width))
inverse_width = bls.Scalar(width).inverse()

roots_of_unity_brp = bit_reversal_permutation(compute_roots_of_unity(FIELD_ELEMENTS_PER_BLOB))

# If we are asked to evaluate within the domain, we already know the answer
if z in roots_of_unity_brp:
eval_index = roots_of_unity_brp.index(z)
return BLSFieldElement(polynomial[eval_index])
return polynomial[eval_index]

result = 0
result = bls.Scalar(0)
for i in range(width):
a = BLSFieldElement(int(polynomial[i]) * int(roots_of_unity_brp[i]) % BLS_MODULUS)
b = BLSFieldElement((int(BLS_MODULUS) + int(z) - int(roots_of_unity_brp[i])) % BLS_MODULUS)
result += int(div(a, b) % BLS_MODULUS)
result = result * int(BLS_MODULUS + pow(z, width, BLS_MODULUS) - 1) * int(inverse_width)
return BLSFieldElement(result % BLS_MODULUS)
a = polynomial[i] * roots_of_unity_brp[i]
b = z - roots_of_unity_brp[i]
result += a / b
r = (BLS_MODULUS + pow(int(z), width, BLS_MODULUS) - 1) % BLS_MODULUS
jtraglia marked this conversation as resolved.
Show resolved Hide resolved
result = result * bls.Scalar(r) * inverse_width
return result
```

### KZG
Expand Down Expand Up @@ -405,19 +378,16 @@ def verify_kzg_proof(commitment_bytes: Bytes48,
#### `verify_kzg_proof_impl`

```python
def verify_kzg_proof_impl(commitment: KZGCommitment,
z: BLSFieldElement,
y: BLSFieldElement,
proof: KZGProof) -> bool:
def verify_kzg_proof_impl(commitment: KZGCommitment, z: bls.Scalar, y: bls.Scalar, proof: KZGProof) -> bool:
"""
Verify KZG proof that ``p(z) == y`` where ``p(z)`` is the polynomial represented by ``polynomial_kzg``.
"""
# Verify: P - y = Q * (X - z)
X_minus_z = bls.add(
bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[1]),
bls.multiply(bls.G2(), (BLS_MODULUS - z) % BLS_MODULUS),
bls.multiply(bls.G2(), -z),
jtraglia marked this conversation as resolved.
Show resolved Hide resolved
)
P_minus_y = bls.add(bls.bytes48_to_G1(commitment), bls.multiply(bls.G1(), (BLS_MODULUS - y) % BLS_MODULUS))
P_minus_y = bls.add(bls.bytes48_to_G1(commitment), bls.multiply(bls.G1(), -y))
return bls.pairing_check([
[P_minus_y, bls.neg(bls.G2())],
[bls.bytes48_to_G1(proof), X_minus_z]
Expand All @@ -428,8 +398,8 @@ def verify_kzg_proof_impl(commitment: KZGCommitment,

```python
def verify_kzg_proof_batch(commitments: Sequence[KZGCommitment],
zs: Sequence[BLSFieldElement],
ys: Sequence[BLSFieldElement],
zs: Sequence[bls.Scalar],
ys: Sequence[bls.Scalar],
proofs: Sequence[KZGProof]) -> bool:
"""
Verify multiple KZG proofs efficiently.
Expand All @@ -445,22 +415,16 @@ def verify_kzg_proof_batch(commitments: Sequence[KZGCommitment],

# Append all inputs to the transcript before we hash
for commitment, z, y, proof in zip(commitments, zs, ys, proofs):
data += commitment \
+ int.to_bytes(z, BYTES_PER_FIELD_ELEMENT, KZG_ENDIANNESS) \
+ int.to_bytes(y, BYTES_PER_FIELD_ELEMENT, KZG_ENDIANNESS) \
+ proof
data += commitment + bls_field_to_bytes(z) + bls_field_to_bytes(y) + proof

r = hash_to_bls_field(data)
r_powers = compute_powers(r, len(commitments))

# Verify: e(sum r^i proof_i, [s]) ==
# e(sum r^i (commitment_i - [y_i]) + sum r^i z_i proof_i, [1])
proof_lincomb = g1_lincomb(proofs, r_powers)
proof_z_lincomb = g1_lincomb(
proofs,
[BLSFieldElement((int(z) * int(r_power)) % BLS_MODULUS) for z, r_power in zip(zs, r_powers)],
)
C_minus_ys = [bls.add(bls.bytes48_to_G1(commitment), bls.multiply(bls.G1(), (BLS_MODULUS - y) % BLS_MODULUS))
proof_z_lincomb = g1_lincomb(proofs, [z * r_power for z, r_power in zip(zs, r_powers)])
C_minus_ys = [bls.add(bls.bytes48_to_G1(commitment), bls.multiply(bls.G1(), -y))
for commitment, y in zip(commitments, ys)]
C_minus_y_as_KZGCommitments = [KZGCommitment(bls.G1_to_bytes48(x)) for x in C_minus_ys]
C_minus_y_lincomb = g1_lincomb(C_minus_y_as_KZGCommitments, r_powers)
Expand All @@ -484,16 +448,13 @@ def compute_kzg_proof(blob: Blob, z_bytes: Bytes32) -> Tuple[KZGProof, Bytes32]:
assert len(z_bytes) == BYTES_PER_FIELD_ELEMENT
polynomial = blob_to_polynomial(blob)
proof, y = compute_kzg_proof_impl(polynomial, bytes_to_bls_field(z_bytes))
return proof, y.to_bytes(BYTES_PER_FIELD_ELEMENT, KZG_ENDIANNESS)
return proof, int(y).to_bytes(BYTES_PER_FIELD_ELEMENT, KZG_ENDIANNESS)
```

#### `compute_quotient_eval_within_domain`

```python
def compute_quotient_eval_within_domain(z: BLSFieldElement,
polynomial: Polynomial,
y: BLSFieldElement
) -> BLSFieldElement:
def compute_quotient_eval_within_domain(z: bls.Scalar, polynomial: Sequence[bls.Scalar], y: bls.Scalar) -> bls.Scalar:
"""
Given `y == p(z)` for a polynomial `p(x)`, compute `q(z)`: the KZG quotient polynomial evaluated at `z` for the
special case where `z` is in roots of unity.
Expand All @@ -502,45 +463,44 @@ def compute_quotient_eval_within_domain(z: BLSFieldElement,
when one of the points is zero". The code below computes q(x_m) for the roots of unity special case.
"""
roots_of_unity_brp = bit_reversal_permutation(compute_roots_of_unity(FIELD_ELEMENTS_PER_BLOB))
result = 0
result = bls.Scalar(0)
for i, omega_i in enumerate(roots_of_unity_brp):
if omega_i == z: # skip the evaluation point in the sum
continue

f_i = int(BLS_MODULUS) + int(polynomial[i]) - int(y) % BLS_MODULUS
numerator = f_i * int(omega_i) % BLS_MODULUS
denominator = int(z) * (int(BLS_MODULUS) + int(z) - int(omega_i)) % BLS_MODULUS
result += int(div(BLSFieldElement(numerator), BLSFieldElement(denominator)))
f_i = polynomial[i] - y
numerator = f_i * omega_i
denominator = z * (z - omega_i)
result += numerator / denominator

return BLSFieldElement(result % BLS_MODULUS)
return result
```

#### `compute_kzg_proof_impl`

```python
def compute_kzg_proof_impl(polynomial: Polynomial, z: BLSFieldElement) -> Tuple[KZGProof, BLSFieldElement]:
def compute_kzg_proof_impl(polynomial: Sequence[bls.Scalar], z: bls.Scalar) -> Tuple[KZGProof, bls.Scalar]:
"""
Helper function for `compute_kzg_proof()` and `compute_blob_kzg_proof()`.
"""
roots_of_unity_brp = bit_reversal_permutation(compute_roots_of_unity(FIELD_ELEMENTS_PER_BLOB))

# For all x_i, compute p(x_i) - p(z)
y = evaluate_polynomial_in_evaluation_form(polynomial, z)
polynomial_shifted = [BLSFieldElement((int(p) - int(y)) % BLS_MODULUS) for p in polynomial]
polynomial_shifted = [p - y for p in polynomial]

# For all x_i, compute (x_i - z)
denominator_poly = [BLSFieldElement((int(x) - int(z)) % BLS_MODULUS)
for x in roots_of_unity_brp]
denominator_poly = [x - z for x in roots_of_unity_brp]

# Compute the quotient polynomial directly in evaluation form
quotient_polynomial = [BLSFieldElement(0)] * FIELD_ELEMENTS_PER_BLOB
quotient_polynomial = [bls.Scalar(0)] * FIELD_ELEMENTS_PER_BLOB
for i, (a, b) in enumerate(zip(polynomial_shifted, denominator_poly)):
if b == 0:
if b == bls.Scalar(0):
# The denominator is zero hence `z` is a root of unity: we must handle it as a special case
quotient_polynomial[i] = compute_quotient_eval_within_domain(roots_of_unity_brp[i], polynomial, y)
else:
# Compute: q(x_i) = (p(x_i) - p(z)) / (x_i - z).
quotient_polynomial[i] = div(a, b)
quotient_polynomial[i] = a / b

return KZGProof(g1_lincomb(bit_reversal_permutation(KZG_SETUP_G1_LAGRANGE), quotient_polynomial)), y
```
Expand Down
Loading
Loading