EIP-4844: Make the spec executable

- Implement all the required glue code to make things executable
- Implement a dummy KZG trusted setup

Co-authored-by: Hsiao-Wei Wang <hsiaowei.eth@gmail.com>
This commit is contained in:
George Kadianakis 2022-07-13 13:14:05 +03:00
parent 5b9bf41de6
commit 567a25f883
19 changed files with 326 additions and 35 deletions

View File

@ -142,7 +142,19 @@ jobs:
command: make citest fork=capella
- store_test_results:
path: tests/core/pyspec/test-reports
test-eip4844:
docker:
- image: circleci/python:3.8
working_directory: ~/specs-repo
steps:
- restore_cache:
key: v3-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_pyspec_cached_venv
- run:
name: Run py-tests
command: make citest fork=eip4844
- store_test_results:
path: tests/core/pyspec/test-reports
table_of_contents:
docker:
- image: circleci/node:10.16.3
@ -260,6 +272,9 @@ workflows:
- test-capella:
requires:
- install_pyspec_test
- test-eip4844:
requires:
- install_pyspec_test
- table_of_contents
- codespell
- lint:

1
.gitignore vendored
View File

@ -19,6 +19,7 @@ tests/core/pyspec/eth2spec/phase0/
tests/core/pyspec/eth2spec/altair/
tests/core/pyspec/eth2spec/bellatrix/
tests/core/pyspec/eth2spec/capella/
tests/core/pyspec/eth2spec/eip4844/
# coverage reports
.htmlcov

View File

@ -136,7 +136,7 @@ codespell:
lint: pyspec
. venv/bin/activate; cd $(PY_SPEC_DIR); \
flake8 --config $(LINTER_CONFIG_FILE) ./eth2spec \
&& pylint --disable=all --enable unused-argument ./eth2spec/phase0 ./eth2spec/altair ./eth2spec/bellatrix \
&& pylint --disable=all --enable unused-argument ./eth2spec/phase0 ./eth2spec/altair ./eth2spec/bellatrix ./eth2spec/capella \
&& mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair -p eth2spec.bellatrix -p eth2spec.capella
lint_generators: pyspec

View File

@ -47,9 +47,9 @@ BELLATRIX_FORK_EPOCH: 18446744073709551615
# Capella
CAPELLA_FORK_VERSION: 0x03000000
CAPELLA_FORK_EPOCH: 18446744073709551615
# Sharding
SHARDING_FORK_VERSION: 0x04000000
SHARDING_FORK_EPOCH: 18446744073709551615
# EIP4844
EIP4844_FORK_VERSION: 0x04000000
EIP4844_FORK_EPOCH: 18446744073709551615

View File

@ -46,9 +46,9 @@ BELLATRIX_FORK_EPOCH: 18446744073709551615
# Capella
CAPELLA_FORK_VERSION: 0x03000001
CAPELLA_FORK_EPOCH: 18446744073709551615
# Sharding
SHARDING_FORK_VERSION: 0x04000001
SHARDING_FORK_EPOCH: 18446744073709551615
# EIP4844
EIP4844_FORK_VERSION: 0x04000001
EIP4844_FORK_EPOCH: 18446744073709551615
# Time parameters

View File

@ -0,0 +1,8 @@
# Mainnet preset - Phase0
# Misc
# ---------------------------------------------------------------
# `uint64(4096)`
FIELD_ELEMENTS_PER_BLOB: 4096
# `uint64(2**4)` (= 16)
MAX_BLOBS_PER_BLOCK: 16

View File

@ -0,0 +1,8 @@
# Minimal preset - Phase0
# Misc
# ---------------------------------------------------------------
# [customized]
FIELD_ELEMENTS_PER_BLOB: 4
# `uint64(2**4)` (= 16)
MAX_BLOBS_PER_BLOCK: 16

View File

@ -45,6 +45,7 @@ PHASE0 = 'phase0'
ALTAIR = 'altair'
BELLATRIX = 'bellatrix'
CAPELLA = 'capella'
EIP4844 = 'eip4844'
# The helper functions that are used when defining constants
@ -208,6 +209,9 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str]) ->
elif source.startswith("class"):
class_name, parent_class = _get_class_info_from_source(source)
# check consistency with spec
if class_name != current_name:
print('class_name', class_name, 'current_name', current_name)
assert class_name == current_name
if parent_class:
assert parent_class == "Container"
@ -230,7 +234,7 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str]) ->
if not _is_constant_id(name):
# Check for short type declarations
if value.startswith(("uint", "Bytes", "ByteList", "Union")):
if value.startswith(("uint", "Bytes", "ByteList", "Union", "Vector", "List")):
custom_types[name] = value
continue
@ -304,7 +308,7 @@ class SpecBuilder(ABC):
@classmethod
@abstractmethod
def hardcoded_custom_type_dep_constants(cls) -> Dict[str, str]: # TODO
def hardcoded_custom_type_dep_constants(cls, spec_object) -> Dict[str, str]: # TODO
"""
The constants that are required for custom types.
"""
@ -432,7 +436,7 @@ get_attesting_indices = cache_this(
return {}
@classmethod
def hardcoded_custom_type_dep_constants(cls) -> Dict[str, str]:
def hardcoded_custom_type_dep_constants(cls, spec_object) -> Dict[str, str]:
return {}
@classmethod
@ -547,11 +551,11 @@ EXECUTION_ENGINE = NoopExecutionEngine()"""
@classmethod
def hardcoded_custom_type_dep_constants(cls) -> str:
def hardcoded_custom_type_dep_constants(cls, spec_object) -> str:
constants = {
'MAX_BYTES_PER_TRANSACTION': 'uint64(2**30)',
'MAX_BYTES_PER_TRANSACTION': spec_object.preset_vars['MAX_BYTES_PER_TRANSACTION'].value,
}
return {**super().hardcoded_custom_type_dep_constants(), **constants}
return {**super().hardcoded_custom_type_dep_constants(spec_object), **constants}
#
@ -567,14 +571,57 @@ from eth2spec.bellatrix import {preset_name} as bellatrix
'''
#
# EIP4844SpecBuilder
#
class EIP4844SpecBuilder(BellatrixSpecBuilder):
fork: str = EIP4844
@classmethod
def imports(cls, preset_name: str):
return super().imports(preset_name) + f'''
from eth2spec.utils import kzg
from eth2spec.bellatrix import {preset_name} as bellatrix
'''
@classmethod
def sundry_functions(cls) -> str:
return super().sundry_functions() + '''
# TODO: for mainnet, load pre-generated trusted setup file to reduce building time.
# TESTING_FIELD_ELEMENTS_PER_BLOB is hardcoded copy from minimal presets
TESTING_FIELD_ELEMENTS_PER_BLOB = 4
TESTING_SECRET = 1337
TESTING_KZG_SETUP_G1 = kzg.generate_setup(bls.G1, TESTING_SECRET, TESTING_FIELD_ELEMENTS_PER_BLOB)
TESTING_KZG_SETUP_G2 = kzg.generate_setup(bls.G2, TESTING_SECRET, TESTING_FIELD_ELEMENTS_PER_BLOB)
TESTING_KZG_SETUP_LAGRANGE = kzg.get_lagrange(TESTING_KZG_SETUP_G1)
KZG_SETUP_G1 = [bls.G1_to_bytes48(p) for p in TESTING_KZG_SETUP_G1]
KZG_SETUP_G2 = [bls.G2_to_bytes96(p) for p in TESTING_KZG_SETUP_G2]
KZG_SETUP_LAGRANGE = TESTING_KZG_SETUP_LAGRANGE
ROOTS_OF_UNITY = kzg.compute_roots_of_unity(TESTING_FIELD_ELEMENTS_PER_BLOB)
def retrieve_blobs_sidecar(slot: Slot, beacon_block_root: Root) -> BlobsSidecar:
pass'''
@classmethod
def hardcoded_custom_type_dep_constants(cls, spec_object) -> str:
constants = {
'FIELD_ELEMENTS_PER_BLOB': spec_object.preset_vars['FIELD_ELEMENTS_PER_BLOB'].value,
'MAX_BLOBS_PER_BLOCK': spec_object.preset_vars['MAX_BLOBS_PER_BLOCK'].value,
}
return {**super().hardcoded_custom_type_dep_constants(spec_object), **constants}
spec_builders = {
builder.fork: builder
for builder in (Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder)
for builder in (Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder, EIP4844SpecBuilder)
}
def is_spec_defined_type(value: str) -> bool:
return value.startswith('ByteList') or value.startswith('Union')
return value.startswith(('ByteList', 'Union', 'Vector', 'List'))
def objects_to_spec(preset_name: str,
@ -652,7 +699,7 @@ def objects_to_spec(preset_name: str,
ordered_class_objects_spec = '\n\n\n'.join(ordered_class_objects.values())
ssz_dep_constants = '\n'.join(map(lambda x: '%s = %s' % (x, builder.hardcoded_ssz_dep_constants()[x]), builder.hardcoded_ssz_dep_constants()))
ssz_dep_constants_verification = '\n'.join(map(lambda x: 'assert %s == %s' % (x, spec_object.ssz_dep_constants[x]), builder.hardcoded_ssz_dep_constants()))
custom_type_dep_constants = '\n'.join(map(lambda x: '%s = %s' % (x, builder.hardcoded_custom_type_dep_constants()[x]), builder.hardcoded_custom_type_dep_constants()))
custom_type_dep_constants = '\n'.join(map(lambda x: '%s = %s' % (x, builder.hardcoded_custom_type_dep_constants(spec_object)[x]), builder.hardcoded_custom_type_dep_constants(spec_object)))
spec = (
builder.imports(preset_name)
+ builder.preparations()
@ -869,14 +916,14 @@ class PySpecCommand(Command):
if len(self.md_doc_paths) == 0:
print("no paths were specified, using default markdown file paths for pyspec"
" build (spec fork: %s)" % self.spec_fork)
if self.spec_fork in (PHASE0, ALTAIR, BELLATRIX, CAPELLA):
if self.spec_fork in (PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844):
self.md_doc_paths = """
specs/phase0/beacon-chain.md
specs/phase0/fork-choice.md
specs/phase0/validator.md
specs/phase0/weak-subjectivity.md
"""
if self.spec_fork in (ALTAIR, BELLATRIX, CAPELLA):
if self.spec_fork in (ALTAIR, BELLATRIX, CAPELLA, EIP4844):
self.md_doc_paths += """
specs/altair/beacon-chain.md
specs/altair/bls.md
@ -885,7 +932,7 @@ class PySpecCommand(Command):
specs/altair/p2p-interface.md
specs/altair/sync-protocol.md
"""
if self.spec_fork in (BELLATRIX, CAPELLA):
if self.spec_fork in (BELLATRIX, CAPELLA, EIP4844):
self.md_doc_paths += """
specs/bellatrix/beacon-chain.md
specs/bellatrix/fork.md
@ -902,6 +949,14 @@ class PySpecCommand(Command):
specs/capella/validator.md
specs/capella/p2p-interface.md
"""
if self.spec_fork == EIP4844:
self.md_doc_paths += """
specs/eip4844/beacon-chain.md
specs/eip4844/fork.md
specs/eip4844/polynomial-commitments.md
specs/eip4844/p2p-interface.md
specs/eip4844/validator.md
"""
if len(self.md_doc_paths) == 0:
raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork)

View File

@ -51,7 +51,7 @@ def pytest_addoption(parser):
def _validate_fork_name(forks):
for fork in forks:
if fork not in ALL_PHASES:
if fork not in set(ALL_PHASES):
raise ValueError(
f'The given --fork argument "{fork}" is not an available fork.'
f' The available forks: {ALL_PHASES}'

View File

@ -7,13 +7,14 @@ from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phas
from eth2spec.altair import mainnet as spec_altair_mainnet, minimal as spec_altair_minimal
from eth2spec.bellatrix import mainnet as spec_bellatrix_mainnet, minimal as spec_bellatrix_minimal
from eth2spec.capella import mainnet as spec_capella_mainnet, minimal as spec_capella_minimal
from eth2spec.eip4844 import mainnet as spec_eip4844_mainnet, minimal as spec_eip4844_minimal
from eth2spec.utils import bls
from .exceptions import SkippedTest
from .helpers.constants import (
PHASE0, ALTAIR, BELLATRIX, CAPELLA,
PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844,
MINIMAL, MAINNET,
ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX, FORKS_BEFORE_CAPELLA,
ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX,
ALL_FORK_UPGRADES,
)
from .helpers.typing import SpecForkName, PresetBaseName
@ -76,12 +77,14 @@ spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = {
ALTAIR: spec_altair_minimal,
BELLATRIX: spec_bellatrix_minimal,
CAPELLA: spec_capella_minimal,
EIP4844: spec_eip4844_minimal,
},
MAINNET: {
PHASE0: spec_phase0_mainnet,
ALTAIR: spec_altair_mainnet,
BELLATRIX: spec_bellatrix_mainnet,
CAPELLA: spec_capella_mainnet,
EIP4844: spec_eip4844_mainnet
},
}
@ -576,12 +579,13 @@ def is_post_bellatrix(spec):
def is_post_capella(spec):
return spec.fork not in FORKS_BEFORE_CAPELLA
return spec.fork == CAPELLA
with_altair_and_later = with_all_phases_except([PHASE0])
with_bellatrix_and_later = with_all_phases_except([PHASE0, ALTAIR])
with_capella_and_later = with_all_phases_except([PHASE0, ALTAIR, BELLATRIX])
with_capella_and_later = with_all_phases_except([PHASE0, ALTAIR, BELLATRIX, EIP4844])
with_eip4844_and_later = with_all_phases_except([PHASE0, ALTAIR, BELLATRIX, CAPELLA])
def only_generator(reason):

View File

@ -14,9 +14,15 @@ CAPELLA = SpecForkName('capella')
SHARDING = SpecForkName('sharding')
CUSTODY_GAME = SpecForkName('custody_game')
DAS = SpecForkName('das')
EIP4844 = SpecForkName('eip4844')
# The forks that pytest runs with.
ALL_PHASES = (PHASE0, ALTAIR, BELLATRIX, CAPELLA)
# The forks that pytest can run with.
ALL_PHASES = (
# Formal forks
PHASE0, ALTAIR, BELLATRIX, CAPELLA,
# Experimental patches
EIP4844,
)
# The forks that output to the test vectors.
TESTGEN_FORKS = (PHASE0, ALTAIR, BELLATRIX)

View File

@ -1,4 +1,4 @@
from eth2spec.test.helpers.constants import FORKS_BEFORE_CAPELLA
from eth2spec.test.context import is_post_capella
def build_empty_execution_payload(spec, state, randao_mix=None):
@ -28,7 +28,7 @@ def build_empty_execution_payload(spec, state, randao_mix=None):
block_hash=spec.Hash32(),
transactions=empty_txs,
)
if spec.fork not in FORKS_BEFORE_CAPELLA:
if is_post_capella(spec):
num_withdrawals = min(spec.MAX_WITHDRAWALS_PER_PAYLOAD, len(state.withdrawal_queue))
payload.withdrawals = state.withdrawal_queue[:num_withdrawals]
@ -55,7 +55,7 @@ def get_execution_payload_header(spec, execution_payload):
block_hash=execution_payload.block_hash,
transactions_root=spec.hash_tree_root(execution_payload.transactions)
)
if spec.fork not in FORKS_BEFORE_CAPELLA:
if is_post_capella(spec):
payload_header.withdrawals_root = spec.hash_tree_root(execution_payload.withdrawals)
return payload_header

View File

@ -13,6 +13,7 @@ from eth2spec.test.helpers.constants import (
ALTAIR,
BELLATRIX,
CAPELLA,
EIP4844,
)
from eth2spec.test.helpers.deposits import (
prepare_state_and_deposit,
@ -150,6 +151,8 @@ def do_fork(state, spec, post_spec, fork_epoch, with_block=True, operation_dict=
state = post_spec.upgrade_to_bellatrix(state)
elif post_spec.fork == CAPELLA:
state = post_spec.upgrade_to_capella(state)
elif post_spec.fork == EIP4844:
state = post_spec.upgrade_to_eip4844(state)
assert state.fork.epoch == fork_epoch

View File

@ -1,6 +1,6 @@
from eth2spec.test.helpers.constants import (
ALTAIR, BELLATRIX,
FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX, FORKS_BEFORE_CAPELLA,
ALTAIR, BELLATRIX, CAPELLA,
FORKS_BEFORE_ALTAIR, FORKS_BEFORE_BELLATRIX,
)
from eth2spec.test.helpers.keys import pubkeys
@ -20,7 +20,7 @@ def build_mock_validator(spec, i: int, balance: int):
effective_balance=min(balance - balance % spec.EFFECTIVE_BALANCE_INCREMENT, spec.MAX_EFFECTIVE_BALANCE)
)
if spec.fork not in FORKS_BEFORE_CAPELLA:
if spec.fork in (CAPELLA):
validator.fully_withdrawn_epoch = spec.FAR_FUTURE_EPOCH
return validator

View File

@ -0,0 +1,81 @@
import random
from eth2spec.utils.ssz.ssz_typing import (
Container,
Bytes20, Bytes32,
ByteList,
List,
Union,
boolean,
uint256, uint64,
)
from eth2spec.utils.ssz.ssz_impl import serialize
#
# Containers from EIP-4844
#
MAX_CALLDATA_SIZE = 2**24
MAX_VERSIONED_HASHES_LIST_SIZE = 2**24
MAX_ACCESS_LIST_STORAGE_KEYS = 2**24
MAX_ACCESS_LIST_SIZE = 2**24
class AccessTuple(Container):
address: Bytes20 # Address = Bytes20
storage_keys: List[Bytes32, MAX_ACCESS_LIST_STORAGE_KEYS]
class ECDSASignature(Container):
y_parity: boolean
r: uint256
s: uint256
class BlobTransaction(Container):
chain_id: uint256
nonce: uint64
priority_fee_per_gas: uint256
max_basefee_per_gas: uint256
gas: uint64
to: Union[None, Bytes20] # Address = Bytes20
value: uint256
data: ByteList[MAX_CALLDATA_SIZE]
access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE]
blob_versioned_hashes: List[Bytes32, MAX_VERSIONED_HASHES_LIST_SIZE]
class SignedBlobTransaction(Container):
message: BlobTransaction
signature: ECDSASignature
def get_sample_blob(spec, rng=None):
if rng is None:
rng = random.Random(5566)
return spec.Blob([
rng.randint(0, spec.BLS_MODULUS - 1)
for _ in range(spec.FIELD_ELEMENTS_PER_BLOB)
])
def get_sample_opaque_tx(spec, blob_count=1, rng=None):
blobs = []
blob_kzg_commitments = []
blob_versioned_hashes = []
for _ in range(blob_count):
blob = get_sample_blob(spec, rng)
blob_commitment = spec.KZGCommitment(spec.blob_to_kzg_commitment(blob))
blob_versioned_hash = spec.kzg_commitment_to_versioned_hash(blob_commitment)
blobs.append(blob)
blob_kzg_commitments.append(blob_commitment)
blob_versioned_hashes.append(blob_versioned_hash)
signed_blob_tx = SignedBlobTransaction(
message=BlobTransaction(
blob_versioned_hashes=blob_versioned_hashes,
)
)
serialized_tx = serialize(signed_blob_tx)
opaque_tx = spec.uint_to_bytes(spec.BLOB_TX_TYPE) + serialized_tx
return opaque_tx, blobs, blob_kzg_commitments

View File

@ -45,6 +45,7 @@ from eth2spec.test.context import (
@with_all_phases
@spec_state_test
def test_prev_slot_block_transition(spec, state):
print('spec.fork', spec.fork)
# Go to clean slot
spec.process_slots(state, state.slot + 1)
# Make a block for it

View File

@ -1,7 +1,7 @@
from eth2spec.test.context import with_all_phases, spec_state_test
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation
from eth2spec.test.helpers.constants import PHASE0, ALTAIR, BELLATRIX, CAPELLA
from eth2spec.test.helpers.constants import ALL_PHASES
from eth2spec.test.helpers.state import transition_to, state_transition_and_sign_block, next_epoch, next_slot
from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store
@ -19,7 +19,7 @@ def run_on_attestation(spec, state, store, attestation, valid=True):
spec.on_attestation(store, attestation)
sample_index = indexed_attestation.attesting_indices[0]
if spec.fork in (PHASE0, ALTAIR, BELLATRIX, CAPELLA):
if spec.fork in ALL_PHASES:
latest_message = spec.LatestMessage(
epoch=attestation.data.target.epoch,
root=attestation.data.beacon_block_root,

View File

@ -1,5 +1,25 @@
from py_ecc.bls import G2ProofOfPossession as py_ecc_bls
from py_ecc.bls.g2_primatives import signature_to_G2 as _signature_to_G2
from py_ecc.optimized_bls12_381 import ( # noqa: F401
G1,
G2,
Z1,
Z2,
add,
multiply,
neg,
pairing,
final_exponentiate,
FQ12
)
from py_ecc.bls.g2_primitives import ( # noqa: F401
G1_to_pubkey as G1_to_bytes48,
pubkey_to_G1 as bytes48_to_G1,
G2_to_signature as G2_to_bytes96,
signature_to_G2 as bytes96_to_G2,
)
import milagro_bls_binding as milagro_bls # noqa: F401 for BLS switching option
# Flag to make BLS active or not. Used for testing, do not ignore BLS in production unless you know what you are doing.
@ -109,3 +129,12 @@ def SkToPk(SK):
return bls.SkToPk(SK)
else:
return bls.SkToPk(SK.to_bytes(32, 'big'))
def pairing_check(values):
p_q_1, p_q_2 = values
final_exponentiation = final_exponentiate(
pairing(p_q_1[1], p_q_1[0], final_exponentiate=False)
* pairing(p_q_2[1], p_q_2[0], final_exponentiate=False)
)
return final_exponentiation == FQ12.one()

View File

@ -0,0 +1,80 @@
# Ref:
# - https://github.com/ethereum/research/blob/8f084630528ba33d92b2bc05edf5338dd193c6f1/trusted_setup/trusted_setup.py
# - https://github.com/asn-d6/kzgverify
from py_ecc.optimized_bls12_381 import ( # noqa: F401
G1,
G2,
Z1,
Z2,
curve_order as BLS_MODULUS,
add,
multiply,
neg,
)
from eth2spec.utils import bls
PRIMITIVE_ROOT_OF_UNITY = 7
def generate_setup(generator, secret, length):
"""
Generate trusted setup of ``generator`` in ``length``.
"""
result = [generator]
for _ in range(1, length):
result.append(multiply(result[-1], secret))
return tuple(result)
def fft(vals, modulus, domain):
"""
FFT for group elements
"""
if len(vals) == 1:
return vals
L = fft(vals[::2], modulus, domain[::2])
R = fft(vals[1::2], modulus, domain[::2])
o = [0] * len(vals)
for i, (x, y) in enumerate(zip(L, R)):
y_times_root = multiply(y, domain[i])
o[i] = add(x, y_times_root)
o[i + len(L)] = add(x, neg(y_times_root))
return o
def compute_root_of_unity(length) -> int:
"""
Generate a w such that ``w**length = 1``.
"""
assert (BLS_MODULUS - 1) % length == 0
return pow(PRIMITIVE_ROOT_OF_UNITY, (BLS_MODULUS - 1) // length, BLS_MODULUS)
def compute_roots_of_unity(field_elements_per_blob):
"""
Compute a list of roots of unity for a given order.
The order must divide the BLS multiplicative group order, i.e. BLS_MODULUS - 1
"""
assert (BLS_MODULUS - 1) % field_elements_per_blob == 0
root_of_unity = compute_root_of_unity(length=field_elements_per_blob)
roots = []
current_root_of_unity = 1
for _ in range(field_elements_per_blob):
roots.append(current_root_of_unity)
current_root_of_unity = current_root_of_unity * root_of_unity % BLS_MODULUS
return roots
def get_lagrange(setup):
"""
Convert a G1 or G2 portion of a setup into the Lagrange basis.
"""
root_of_unity = compute_root_of_unity(len(setup))
assert pow(root_of_unity, len(setup), BLS_MODULUS) == 1
domain = [pow(root_of_unity, i, BLS_MODULUS) for i in range(len(setup))]
# TODO: introduce an IFFT function for simplicity
fft_output = fft(setup, BLS_MODULUS, domain)
inv_length = pow(len(setup), BLS_MODULUS - 2, BLS_MODULUS)
return [bls.G1_to_bytes48(multiply(fft_output[-i], inv_length)) for i in range(len(fft_output))]