Merge pull request #1803 from ethereum/fast-bls-option

BLS options for pyspec
This commit is contained in:
Danny Ryan 2020-05-20 14:39:49 -06:00 committed by GitHub
commit 7770accf96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 106 additions and 33 deletions

View File

@ -75,15 +75,15 @@ install_test:
test: pyspec
. venv/bin/activate; cd $(PY_SPEC_DIR); \
python -m pytest -n 4 --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
python -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
find_test: pyspec
. venv/bin/activate; cd $(PY_SPEC_DIR); \
python -m pytest -k=$(K) --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
python -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.spec --cov=eth2spec.phase1.spec --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec
citest: pyspec
mkdir -p tests/core/pyspec/test-reports/eth2spec; . venv/bin/activate; cd $(PY_SPEC_DIR); \
python -m pytest -n 4 --junitxml=eth2spec/test_results.xml eth2spec
python -m pytest -n 4 --disable-bls --junitxml=eth2spec/test_results.xml eth2spec
open_cov:
((open "$(COV_INDEX_FILE)" || xdg-open "$(COV_INDEX_FILE)") &> /dev/null) &

View File

@ -485,6 +485,7 @@ setup(
url="https://github.com/ethereum/eth2.0-specs",
include_package_data=False,
package_data={'configs': ['*.yaml'],
'specs': ['**/*.md'],
'eth2spec': ['VERSION.txt']},
package_dir={
@ -505,6 +506,7 @@ setup(
"eth-typing>=2.1.0,<3.0.0",
"pycryptodome==3.9.4",
"py_ecc==4.0.0",
"milagro_bls_binding==1.3.0",
"dataclasses==0.6",
"remerkleable==0.1.16",
"ruamel.yaml==0.16.5",

View File

@ -55,6 +55,11 @@ Run the test command from the `tests/core/pyspec` directory:
pytest --config=minimal eth2spec
```
Options:
- `--config`, to change the config. Defaults to `minimal`, can be set to `mainnet`, or other configs from the configs directory.
- `--disable-bls`, to disable BLS (only for tests that can run without)
- `--bls-type`, `milagro` or `py_ecc` (default)
### How to view code coverage report
Run `make open_cov` from the root of the specs repository after running `make test` to open the html code coverage report.

View File

@ -1,6 +1,6 @@
from eth2spec.config import config_util
from eth2spec.test.context import reload_specs
from eth2spec.test import context
from eth2spec.utils import bls as bls_utils
# We import pytest only when it's present, i.e. when we are running tests.
# The test-cases themselves can be generated without installing pytest.
@ -27,7 +27,16 @@ def fixture(*args, **kwargs):
def pytest_addoption(parser):
parser.addoption(
"--config", action="store", default="minimal", help="config: make the pyspec use the specified configuration"
"--config", action="store", type=str, default="minimal",
help="config: make the pyspec use the specified configuration"
)
parser.addoption(
"--disable-bls", action="store_true",
help="bls-default: make tests that are not dependent on BLS run without BLS"
)
parser.addoption(
"--bls-type", action="store", type=str, default="py_ecc", choices=["py_ecc", "milagro"],
help="bls-type: use 'pyecc' or 'milagro' implementation for BLS"
)
@ -36,4 +45,22 @@ def config(request):
config_name = request.config.getoption("--config")
config_util.prepare_config('../../../configs/', config_name)
# now that the presets are loaded, reload the specs to apply them
reload_specs()
context.reload_specs()
@fixture(autouse=True)
def bls_default(request):
disable_bls = request.config.getoption("--disable-bls")
if disable_bls:
context.DEFAULT_BLS_ACTIVE = False
@fixture(autouse=True)
def bls_type(request):
bls_type = request.config.getoption("--bls-type")
if bls_type == "py_ecc":
bls_utils.bls = bls_utils.py_ecc_bls
elif bls_type == "milagro":
bls_utils.bls = bls_utils.milagro_bls
else:
raise Exception(f"unrecognized bls type: {bls_type}")

View File

@ -167,14 +167,15 @@ def single_phase(fn):
return entry
# BLS is turned off by default *for performance purposes during TESTING*.
# BLS is turned on by default, it can be disabled in tests by overriding this, or using `--disable-bls`.
# *This is for performance purposes during TESTING, DO NOT DISABLE IN PRODUCTION*.
# The runner of the test can indicate the preferred setting (test generators prefer BLS to be ON).
# - Some tests are marked as BLS-requiring, and ignore this setting.
# (tests that express differences caused by BLS, e.g. invalid signatures being rejected)
# - Some other tests are marked as BLS-ignoring, and ignore this setting.
# (tests that are heavily performance impacted / require unsigned state transitions)
# - Most tests respect the BLS setting.
DEFAULT_BLS_ACTIVE = False
DEFAULT_BLS_ACTIVE = True
def spec_test(fn):

View File

@ -183,7 +183,7 @@ def test_filtered_block_tree(spec, state):
for i in range(spec.SLOTS_PER_EPOCH):
slot = rogue_block.slot + i
for index in range(spec.get_committee_count_at_slot(non_viable_state, slot)):
attestation = get_valid_attestation(spec, non_viable_state, rogue_block.slot + i, index)
attestation = get_valid_attestation(spec, non_viable_state, slot, index, signed=True)
attestations.append(attestation)
# tick time forward to be able to include up to the latest attestation

View File

@ -1,6 +1,6 @@
from eth2spec.test.context import (
PHASE0, PHASE1,
spec_state_test, expect_assertion_error, always_bls, with_all_phases, with_phases
spec_state_test, expect_assertion_error, always_bls, never_bls, with_all_phases, with_phases
)
from eth2spec.test.helpers.attestations import sign_indexed_attestation
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, \
@ -89,6 +89,7 @@ def test_success_double(spec, state):
@with_all_phases
@spec_state_test
@never_bls
def test_success_surround(spec, state):
next_epoch_via_block(spec, state)

View File

@ -1,4 +1,4 @@
from eth2spec.test.context import spec_state_test, never_bls, with_all_phases
from eth2spec.test.context import spec_state_test, always_bls, with_all_phases
from eth2spec.test.helpers.attestations import build_attestation_data
from eth2spec.test.helpers.block import build_empty_block
from eth2spec.test.helpers.deposits import prepare_state_and_deposit
@ -8,9 +8,11 @@ from eth2spec.utils import bls
from eth2spec.utils.ssz.ssz_typing import Bitlist
def run_get_signature_test(spec, state, obj, domain, get_signature_fn, privkey, pubkey):
def run_get_signature_test(spec, state, obj, domain, get_signature_fn, privkey, pubkey, signing_ssz_object=None):
if signing_ssz_object is None:
signing_ssz_object = obj
signature = get_signature_fn(state, obj, privkey)
signing_root = spec.compute_signing_root(obj, domain)
signing_root = spec.compute_signing_root(signing_ssz_object, domain)
assert bls.Verify(pubkey, signing_root, signature)
@ -55,7 +57,6 @@ def get_mock_aggregate(spec):
@with_all_phases
@spec_state_test
@never_bls
def test_check_if_validator_active(spec, state):
active_validator_index = len(state.validators) - 1
assert spec.check_if_validator_active(state, active_validator_index)
@ -73,7 +74,6 @@ def test_check_if_validator_active(spec, state):
@with_all_phases
@spec_state_test
@never_bls
def test_get_committee_assignment_current_epoch(spec, state):
epoch = spec.get_current_epoch(state)
validator_index = len(state.validators) - 1
@ -82,7 +82,6 @@ def test_get_committee_assignment_current_epoch(spec, state):
@with_all_phases
@spec_state_test
@never_bls
def test_get_committee_assignment_next_epoch(spec, state):
epoch = spec.get_current_epoch(state) + 1
validator_index = len(state.validators) - 1
@ -91,7 +90,6 @@ def test_get_committee_assignment_next_epoch(spec, state):
@with_all_phases
@spec_state_test
@never_bls
def test_get_committee_assignment_out_bound_epoch(spec, state):
epoch = spec.get_current_epoch(state) + 2
validator_index = len(state.validators) - 1
@ -100,7 +98,6 @@ def test_get_committee_assignment_out_bound_epoch(spec, state):
@with_all_phases
@spec_state_test
@never_bls
def test_is_proposer(spec, state):
proposer_index = spec.get_beacon_proposer_index(state)
assert spec.is_proposer(state, proposer_index)
@ -132,6 +129,7 @@ def test_get_epoch_signature(spec, state):
get_signature_fn=spec.get_epoch_signature,
privkey=privkey,
pubkey=pubkey,
signing_ssz_object=spec.compute_epoch_at_slot(block.slot),
)
@ -299,6 +297,7 @@ def test_compute_new_state_root(spec, state):
@with_all_phases
@spec_state_test
@always_bls
def test_get_block_signature(spec, state):
privkey = privkeys[0]
pubkey = pubkeys[0]
@ -320,6 +319,7 @@ def test_get_block_signature(spec, state):
@with_all_phases
@spec_state_test
@always_bls
def test_get_attestation_signature(spec, state):
privkey = privkeys[0]
pubkey = pubkeys[0]
@ -341,6 +341,7 @@ def test_get_attestation_signature(spec, state):
@with_all_phases
@spec_state_test
@always_bls
def test_get_slot_signature(spec, state):
privkey = privkeys[0]
pubkey = pubkeys[0]
@ -359,6 +360,7 @@ def test_get_slot_signature(spec, state):
@with_all_phases
@spec_state_test
@always_bls
def test_is_aggregator(spec, state):
# TODO: we can test the probabilistic result against `TARGET_AGGREGATORS_PER_COMMITTEE`
# if we have more validators and larger committeee size
@ -377,9 +379,10 @@ def test_is_aggregator(spec, state):
@with_all_phases
@spec_state_test
@always_bls
def test_get_aggregate_signature(spec, state):
attestations = []
pubkeys = []
attesting_pubkeys = []
slot = state.slot
committee_index = 0
attestation_data = build_attestation_data(spec, state, slot=slot, index=committee_index)
@ -391,24 +394,26 @@ def test_get_aggregate_signature(spec, state):
committee_size = len(beacon_committee)
aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size))
for i, validator_index in enumerate(beacon_committee):
bits = aggregation_bits
bits = aggregation_bits.copy()
bits[i] = True
attestations.append(
spec.Attestation(
data=attestation_data,
aggregation_bits=bits,
signature=spec.get_attestation_signature(state, attestation_data, privkeys[validator_index]),
)
)
pubkeys.append(state.validators[validator_index].pubkey)
pubkey = bls.AggregatePKs(pubkeys)
attesting_pubkeys.append(state.validators[validator_index].pubkey)
assert len(attestations) > 0
signature = spec.get_aggregate_signature(attestations)
domain = spec.get_domain(state, spec.DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch)
signing_root = spec.compute_signing_root(attestation_data, domain)
assert bls.Verify(pubkey, signing_root, signature)
assert bls.FastAggregateVerify(attesting_pubkeys, signing_root, signature)
@with_all_phases
@spec_state_test
@always_bls
def test_get_aggregate_and_proof(spec, state):
privkey = privkeys[0]
aggregator_index = spec.ValidatorIndex(10)
@ -421,6 +426,7 @@ def test_get_aggregate_and_proof(spec, state):
@with_all_phases
@spec_state_test
@always_bls
def test_get_aggregate_and_proof_signature(spec, state):
privkey = privkeys[0]
pubkey = pubkeys[0]

View File

@ -1,12 +1,17 @@
from py_ecc.bls import G2ProofOfPossession as bls
from py_ecc.bls import G2ProofOfPossession as py_ecc_bls
from py_ecc.bls.g2_primatives import signature_to_G2 as _signature_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.
bls_active = True
# To change bls implementation, default to PyECC for correctness. Milagro is a good faster alternative.
bls = py_ecc_bls
STUB_SIGNATURE = b'\x11' * 96
STUB_PUBKEY = b'\x22' * 48
STUB_COORDINATES = _signature_to_G2(bls.Sign(0, b""))
Z2_SIGNATURE = b'\xc0' + b'\x00' * 95
STUB_COORDINATES = _signature_to_G2(Z2_SIGNATURE)
def only_with_bls(alt_return=None):
@ -36,7 +41,7 @@ def Verify(PK, message, signature):
@only_with_bls(alt_return=True)
def AggregateVerify(pubkeys, messages, signature):
try:
result = bls.AggregateVerify(pubkeys, messages, signature)
result = bls.AggregateVerify(list(pubkeys), list(messages), signature)
except Exception:
result = False
finally:
@ -46,7 +51,7 @@ def AggregateVerify(pubkeys, messages, signature):
@only_with_bls(alt_return=True)
def FastAggregateVerify(pubkeys, message, signature):
try:
result = bls.FastAggregateVerify(pubkeys, message, signature)
result = bls.FastAggregateVerify(list(pubkeys), message, signature)
except Exception:
result = False
finally:
@ -60,7 +65,10 @@ def Aggregate(signatures):
@only_with_bls(alt_return=STUB_SIGNATURE)
def Sign(SK, message):
return bls.Sign(SK, message)
if bls == py_ecc_bls:
return bls.Sign(SK, message)
else:
return bls.Sign(SK.to_bytes(32, 'big'), message)
@only_with_bls(alt_return=STUB_COORDINATES)
@ -70,7 +78,7 @@ def signature_to_G2(signature):
@only_with_bls(alt_return=STUB_PUBKEY)
def AggregatePKs(pubkeys):
return bls._AggregatePKs(pubkeys)
return bls._AggregatePKs(list(pubkeys))
@only_with_bls(alt_return=STUB_SIGNATURE)

View File

@ -2,18 +2,22 @@
BLS test vectors generator
"""
from hashlib import sha256
from typing import Tuple, Iterable, Any, Callable, Dict
from eth_utils import (
encode_hex,
int_to_big_endian,
)
from gen_base import gen_runner, gen_typing
import milagro_bls_binding as milagro_bls
from eth2spec.utils import bls
from hashlib import sha256
from eth2spec.test.context import PHASE0
from gen_base import gen_runner, gen_typing
def to_bytes(i):
return i.to_bytes(32, "big")
def hash(x):
@ -70,8 +74,15 @@ def case02_verify():
# Valid signature
signature = bls.Sign(privkey, message)
pubkey = bls.SkToPk(privkey)
assert milagro_bls.SkToPk(to_bytes(privkey)) == pubkey
assert milagro_bls.Sign(to_bytes(privkey), message) == signature
identifier = f'{encode_hex(pubkey)}_{encode_hex(message)}'
assert bls.Verify(pubkey, message, signature)
assert milagro_bls.Verify(pubkey, message, signature)
yield f'verify_valid_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
'input': {
'pubkey': encode_hex(pubkey),
@ -85,6 +96,7 @@ def case02_verify():
wrong_pubkey = bls.SkToPk(PRIVKEYS[(i + 1) % len(PRIVKEYS)])
identifier = f'{encode_hex(wrong_pubkey)}_{encode_hex(message)}'
assert not bls.Verify(wrong_pubkey, message, signature)
assert not milagro_bls.Verify(wrong_pubkey, message, signature)
yield f'verify_wrong_pubkey_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
'input': {
'pubkey': encode_hex(wrong_pubkey),
@ -98,6 +110,7 @@ def case02_verify():
tampered_signature = signature[:-4] + b'\xFF\xFF\xFF\xFF'
identifier = f'{encode_hex(pubkey)}_{encode_hex(message)}'
assert not bls.Verify(pubkey, message, tampered_signature)
assert not milagro_bls.Verify(pubkey, message, tampered_signature)
yield f'verify_tampered_signature_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
'input': {
'pubkey': encode_hex(pubkey),
@ -109,6 +122,7 @@ def case02_verify():
# Valid pubkey and signature with the point at infinity
assert bls.Verify(Z1_PUBKEY, message, Z2_SIGNATURE)
assert milagro_bls.Verify(Z1_PUBKEY, message, Z2_SIGNATURE)
yield f'verify_infinity_pubkey_and_infinity_signature', {
'input': {
'pubkey': encode_hex(Z1_PUBKEY),
@ -152,6 +166,7 @@ def case04_fast_aggregate_verify():
# Valid signature
identifier = f'{pubkeys_serial}_{encode_hex(message)}'
assert bls.FastAggregateVerify(pubkeys, message, aggregate_signature)
assert milagro_bls.FastAggregateVerify(pubkeys, message, aggregate_signature)
yield f'fast_aggregate_verify_valid_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
'input': {
'pubkeys': pubkeys_serial,
@ -166,6 +181,7 @@ def case04_fast_aggregate_verify():
pubkeys_extra_serial = [encode_hex(pubkey) for pubkey in pubkeys_extra]
identifier = f'{pubkeys_extra_serial}_{encode_hex(message)}'
assert not bls.FastAggregateVerify(pubkeys_extra, message, aggregate_signature)
assert not milagro_bls.FastAggregateVerify(pubkeys_extra, message, aggregate_signature)
yield f'fast_aggregate_verify_extra_pubkey_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
'input': {
'pubkeys': pubkeys_extra_serial,
@ -179,6 +195,7 @@ def case04_fast_aggregate_verify():
tampered_signature = aggregate_signature[:-4] + b'\xff\xff\xff\xff'
identifier = f'{pubkeys_serial}_{encode_hex(message)}'
assert not bls.FastAggregateVerify(pubkeys, message, tampered_signature)
assert not milagro_bls.FastAggregateVerify(pubkeys, message, tampered_signature)
yield f'fast_aggregate_verify_tampered_signature_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
'input': {
'pubkeys': pubkeys_serial,
@ -190,6 +207,7 @@ def case04_fast_aggregate_verify():
# Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == Z1_SIGNATURE
assert not bls.FastAggregateVerify([], message, Z2_SIGNATURE)
assert not milagro_bls.FastAggregateVerify([], message, Z2_SIGNATURE)
yield f'fast_aggregate_verify_na_pubkeys_and_infinity_signature', {
'input': {
'pubkeys': [],
@ -201,6 +219,7 @@ def case04_fast_aggregate_verify():
# Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00...
assert not bls.FastAggregateVerify([], message, NO_SIGNATURE)
assert not milagro_bls.FastAggregateVerify([], message, NO_SIGNATURE)
yield f'fast_aggregate_verify_na_pubkeys_and_na_signature', {
'input': {
'pubkeys': [],
@ -228,6 +247,7 @@ def case05_aggregate_verify():
aggregate_signature = bls.Aggregate(sigs)
assert bls.AggregateVerify(pubkeys, messages, aggregate_signature)
assert milagro_bls.AggregateVerify(pubkeys, messages, aggregate_signature)
yield f'aggregate_verify_valid', {
'input': {
'pubkeys': pubkeys_serial,
@ -239,6 +259,7 @@ def case05_aggregate_verify():
tampered_signature = aggregate_signature[:4] + b'\xff\xff\xff\xff'
assert not bls.AggregateVerify(pubkey, messages, tampered_signature)
assert not milagro_bls.AggregateVerify(pubkeys, messages, tampered_signature)
yield f'aggregate_verify_tampered_signature', {
'input': {
'pubkeys': pubkeys_serial,
@ -250,6 +271,7 @@ def case05_aggregate_verify():
# Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == Z1_SIGNATURE
assert not bls.AggregateVerify([], [], Z2_SIGNATURE)
assert not milagro_bls.AggregateVerify([], [], Z2_SIGNATURE)
yield f'aggregate_verify_na_pubkeys_and_infinity_signature', {
'input': {
'pubkeys': [],
@ -261,6 +283,7 @@ def case05_aggregate_verify():
# Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00...
assert not bls.AggregateVerify([], [], NO_SIGNATURE)
assert not milagro_bls.AggregateVerify([], [], NO_SIGNATURE)
yield f'aggregate_verify_na_pubkeys_and_na_signature', {
'input': {
'pubkeys': [],