Add whisk tests

This commit is contained in:
dapplion 2023-06-08 18:56:02 +03:00
parent f1aabcd718
commit 4ce2b02b44
19 changed files with 614 additions and 34 deletions

View File

@ -168,6 +168,19 @@ jobs:
command: make citest fork=eip6110
- store_test_results:
path: tests/core/pyspec/test-reports
test-whisk:
docker:
- image: circleci/python:3.9
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=whisk
- store_test_results:
path: tests/core/pyspec/test-reports
table_of_contents:
docker:
- image: circleci/node:10.16.3
@ -291,6 +304,9 @@ workflows:
- test-eip6110:
requires:
- install_pyspec_test
- test-whisk:
requires:
- install_pyspec_test
- table_of_contents
- codespell
- lint:

View File

@ -71,7 +71,7 @@ jobs:
needs: [preclear,lint,codespell,table_of_contents]
strategy:
matrix:
version: ["phase0", "altair", "bellatrix", "capella", "deneb", "eip6110"]
version: ["phase0", "altair", "bellatrix", "capella", "deneb", "eip6110", "whisk"]
steps:
- name: Checkout this repo
uses: actions/checkout@v3.2.0

View File

@ -257,6 +257,11 @@ def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str], pr
if any('KZG_SETUP' in name for name in constant_vars):
_update_constant_vars_with_kzg_setups(constant_vars, preset_name)
if any('CURDLEPROOFS_CRS' in name for name in constant_vars):
# TODO: Use actual CRS derived from a fixed string like 'nankokita_no_kakurenbo'
crs_len = int(preset_vars['WHISK_VALIDATORS_PER_SHUFFLE'].value) + int(preset_vars['CURDLEPROOFS_N_BLINDERS'].value) + 3
constant_vars['CURDLEPROOFS_CRS_G1'] = VariableDefinition(constant_vars['CURDLEPROOFS_CRS_G1'].value, str(ALL_KZG_SETUPS['mainnet'][0][0:crs_len]), "noqa: E501", None)
return SpecObject(
functions=functions,
protocols=protocols,
@ -519,6 +524,6 @@ setup(
"lru-dict==1.2.0",
MARKO_VERSION,
"py_arkworks_bls12381==0.3.4",
"curdleproofs @ git+https://github.com/nalinbhardwaj/curdleproofs.pie@805d06785b6ff35fde7148762277dd1ae678beeb#egg=curdleproofs&subdirectory=curdleproofs",
"curdleproofs @ git+https://github.com/nalinbhardwaj/curdleproofs.pie@5fe661b7183454655ff1e47690bb28e01e66ea66#egg=curdleproofs&subdirectory=curdleproofs",
]
)

View File

@ -33,7 +33,7 @@
This document details the beacon chain additions and changes of to support the Whisk SSLE,
*Note:* This specification is built upon [Capella](../../capella/beacon-chain.md) and is under active development.
*Note:* This specification is built upon [capella](../../capella/beacon-chain.md) and is under active development.
## Constants
@ -83,14 +83,15 @@ def bytes_to_bls_field(b: Bytes32) -> BLSFieldElement:
TODO: Deneb will introduces this helper too. Should delete it once it's rebased to post-Deneb.
"""
field_element = int.from_bytes(b, ENDIANNESS)
assert field_element < BLS_MODULUS
return BLSFieldElement(field_element)
return BLSFieldElement(field_element % BLS_MODULUS)
```
| Name | Value |
| ------------------ | ------------------------------------------------------------------------------- |
| `BLS_G1_GENERATOR` | `bls.G1_to_bytes48(bls.G1)` |
| `BLS_G1_GENERATOR` | `Bytes48('0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb')` |
| `BLS_MODULUS` | `52435875175126190479447740508185965837690552500527637822603658699938581184513` |
| `CURDLEPROOFS_CRS_G1` | `Vector[G1Point, WHISK_VALIDATORS_PER_SHUFFLE + CURDLEPROOFS_N_BLINDERS + 3]`, contents TBD
| `CURDLEPROOFS_CRS` | `curdleproofs.CurdleproofsCrs.from_points_compressed(WHISK_VALIDATORS_PER_SHUFFLE, CURDLEPROOFS_N_BLINDERS, CURDLEPROOFS_CRS_G1)` |
### Curdleproofs and opening proofs
@ -105,8 +106,16 @@ def IsValidWhiskShuffleProof(pre_shuffle_trackers: Sequence[WhiskTracker],
Verify `post_shuffle_trackers` is a permutation of `pre_shuffle_trackers`.
Defined in https://github.com/nalinbhardwaj/curdleproofs.pie/tree/verifier-only.
"""
# pylint: disable=unused-argument
return True
try:
return curdleproofs.IsValidWhiskShuffleProof(
CURDLEPROOFS_CRS,
pre_shuffle_trackers,
post_shuffle_trackers,
M,
shuffle_proof,
)
except:
return False
```
```python
@ -117,8 +126,11 @@ def IsValidWhiskOpeningProof(tracker: WhiskTracker,
Verify knowledge of `k` such that `tracker.k_r_G == k * tracker.r_G` and `k_commitment == k * BLS_G1_GENERATOR`.
Defined in https://github.com/nalinbhardwaj/curdleproofs.pie/tree/verifier-only.
"""
# pylint: disable=unused-argument
return True
try:
return curdleproofs.IsValidWhiskOpeningProof(tracker, k_commitment, tracker_proof)
except:
return False
```
## Epoch processing
@ -276,7 +288,7 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None:
#### `BeaconBlockBody`
```python
class BeaconBlockBody(capella.BeaconBlockBody):
class BeaconBlockBody(Container):
randao_reveal: BLSSignature
eth1_data: Eth1Data # Eth1 data vote
graffiti: Bytes32 # Arbitrary data
@ -289,7 +301,6 @@ class BeaconBlockBody(capella.BeaconBlockBody):
sync_aggregate: SyncAggregate
# Execution
execution_payload: ExecutionPayload
# Capella operations
bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES]
# Whisk
whisk_opening_proof: WhiskTrackerProof # [New in Whisk]
@ -350,10 +361,7 @@ def is_k_commitment_unique(state: BeaconState, k_commitment: BLSG1Point) -> bool
```
```python
def process_whisk(state: BeaconState, body: BeaconBlockBody) -> None:
process_shuffled_trackers(state, body)
# Overwrite all validator Whisk fields (first Whisk proposal) or just the permutation commitment (next proposals)
def process_whisk_registration(state: BeaconState, body: BeaconBlockBody) -> None:
proposer = state.validators[get_beacon_proposer_index(state)]
if proposer.whisk_tracker.r_G == BLS_G1_GENERATOR: # first Whisk proposal
assert body.whisk_tracker.r_G != BLS_G1_GENERATOR
@ -369,20 +377,19 @@ def process_whisk(state: BeaconState, body: BeaconBlockBody) -> None:
assert body.whisk_registration_proof == WhiskTrackerProof()
assert body.whisk_tracker == WhiskTracker()
assert body.whisk_k_commitment == BLSG1Point()
assert body.whisk_shuffle_proof_M_commitment == BLSG1Point()
```
```python
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
if is_execution_enabled(state, block.body):
process_withdrawals(state, block.body.execution_payload)
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE)
process_withdrawals(state, block.body.execution_payload)
process_execution_payload(state, block.body, EXECUTION_ENGINE)
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body)
process_sync_aggregate(state, block.body.sync_aggregate)
process_whisk(state, block.body) # [New in Whisk]
process_shuffled_trackers(state, block.body) # [New in Whisk]
process_whisk_registration(state, block.body) # [New in Whisk]
```
### Deposits

View File

@ -9,12 +9,14 @@ from eth2spec.bellatrix import mainnet as spec_bellatrix_mainnet, minimal as spe
from eth2spec.capella import mainnet as spec_capella_mainnet, minimal as spec_capella_minimal
from eth2spec.deneb import mainnet as spec_deneb_mainnet, minimal as spec_deneb_minimal
from eth2spec.eip6110 import mainnet as spec_eip6110_mainnet, minimal as spec_eip6110_minimal
from eth2spec.whisk import mainnet as spec_whisk_mainnet, minimal as spec_whisk_minimal
from eth2spec.utils import bls
from .exceptions import SkippedTest
from .helpers.constants import (
PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB,
EIP6110,
WHISK,
MINIMAL, MAINNET,
ALL_PHASES,
ALL_FORK_UPGRADES,
@ -83,6 +85,7 @@ spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = {
CAPELLA: spec_capella_minimal,
DENEB: spec_deneb_minimal,
EIP6110: spec_eip6110_minimal,
WHISK: spec_whisk_minimal,
},
MAINNET: {
PHASE0: spec_phase0_mainnet,
@ -91,6 +94,7 @@ spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = {
CAPELLA: spec_capella_mainnet,
DENEB: spec_deneb_mainnet,
EIP6110: spec_eip6110_mainnet,
WHISK: spec_whisk_mainnet,
},
}
@ -541,6 +545,7 @@ with_bellatrix_and_later = with_all_phases_from(BELLATRIX)
with_capella_and_later = with_all_phases_from(CAPELLA)
with_deneb_and_later = with_all_phases_from(DENEB)
with_eip6110_and_later = with_all_phases_from(EIP6110)
with_whisk_and_later = with_all_phases_from(WHISK)
class quoted_str(str):

View File

@ -1,10 +1,25 @@
from eth2spec.test.helpers.execution_payload import build_empty_execution_payload
from eth2spec.test.helpers.forks import is_post_altair, is_post_bellatrix
from eth2spec.test.helpers.keys import privkeys
from eth2spec.test.helpers.forks import is_post_whisk, is_post_altair, is_post_bellatrix
from eth2spec.test.helpers.keys import privkeys, whisk_ks_initial, whisk_ks_final
from eth2spec.utils import bls
from eth2spec.utils.bls import only_with_bls
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from curdleproofs import (
GenerateWhiskTrackerProof,
WhiskTracker,
IsValidWhiskOpeningProof,
GenerateWhiskShuffleProof,
IsValidWhiskShuffleProof
)
from py_ecc.optimized_bls12_381.optimized_curve import G1, multiply
from py_ecc.typing import Optimized_Field, Optimized_Point3D
from py_ecc.bls.g2_primitives import (
G1_to_pubkey as py_ecc_G1_to_bytes48,
pubkey_to_G1 as py_ecc_bytes48_to_G1,
)
from eth2spec.test.helpers.whisk import get_whisk_tracker_and_commitment
PointProjective = Optimized_Point3D[Optimized_Field]
def get_proposer_index_maybe(spec, state, slot, proposer_index=None):
if proposer_index is None:
@ -24,10 +39,9 @@ def get_proposer_index_maybe(spec, state, slot, proposer_index=None):
@only_with_bls()
def apply_randao_reveal(spec, state, block, proposer_index=None):
def apply_randao_reveal(spec, state, block, proposer_index):
assert state.slot <= block.slot
proposer_index = get_proposer_index_maybe(spec, state, block.slot, proposer_index)
privkey = privkeys[proposer_index]
domain = spec.get_domain(state, spec.DOMAIN_RANDAO, spec.compute_epoch_at_slot(block.slot))
@ -72,7 +86,7 @@ def apply_empty_block(spec, state, slot=None):
return transition_unsigned_block(spec, state, block)
def build_empty_block(spec, state, slot=None):
def build_empty_block(spec, state, slot=None, proposer_index=None):
"""
Build empty block for ``slot``, built upon the latest block header seen by ``state``.
Slot must be greater than or equal to the current slot in ``state``.
@ -87,13 +101,14 @@ def build_empty_block(spec, state, slot=None):
spec.process_slots(state, slot)
state, parent_block_root = get_state_and_beacon_parent_root_at_slot(spec, state, slot)
proposer_index = get_beacon_proposer_to_build(spec, state, proposer_index)
empty_block = spec.BeaconBlock()
empty_block.slot = slot
empty_block.proposer_index = spec.get_beacon_proposer_index(state)
empty_block.proposer_index = proposer_index
empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index
empty_block.parent_root = parent_block_root
apply_randao_reveal(spec, state, empty_block)
apply_randao_reveal(spec, state, empty_block, proposer_index)
if is_post_altair(spec):
empty_block.body.sync_aggregate.sync_committee_signature = spec.G2_POINT_AT_INFINITY
@ -101,11 +116,112 @@ def build_empty_block(spec, state, slot=None):
if is_post_bellatrix(spec):
empty_block.body.execution_payload = build_empty_execution_payload(spec, state)
if is_post_whisk(spec):
# Whisk opening proof
#######
# Create valid whisk opening proof
# TODO: Use k_initial or k_final to handle first and subsequent proposals
k_initial = whisk_ks_initial[proposer_index]
# Sanity check proposer is correct
proposer_k_commitment = state.validators[proposer_index].whisk_k_commitment
k_commitment = py_ecc_G1_to_bytes48(multiply(G1, int(k_initial)))
if proposer_k_commitment != k_commitment:
raise Exception("k proposer_index does not match proposer_k_commitment", proposer_k_commitment, k_commitment)
proposer_tracker = state.whisk_proposer_trackers[state.slot % spec.WHISK_PROPOSER_TRACKERS_COUNT]
if not is_whisk_proposer(proposer_tracker, k_initial):
raise Exception("k proposer_index does not match proposer_tracker")
empty_block.body.whisk_opening_proof = GenerateWhiskTrackerProof(proposer_tracker, k_initial)
if not IsValidWhiskOpeningProof(proposer_tracker, proposer_k_commitment, empty_block.body.whisk_opening_proof):
raise Exception(
"produced opening proof is not valid",
proposer_tracker,
proposer_k_commitment,
empty_block.body.whisk_opening_proof
)
# Whisk shuffle proof
#######
shuffle_indices = spec.get_shuffle_indices(empty_block.body.randao_reveal)
pre_shuffle_trackers = [state.whisk_candidate_trackers[i] for i in shuffle_indices]
post_trackers, m, shuffle_proof = GenerateWhiskShuffleProof(spec.CURDLEPROOFS_CRS, pre_shuffle_trackers)
empty_block.body.whisk_post_shuffle_trackers = post_trackers
empty_block.body.whisk_shuffle_proof = shuffle_proof
empty_block.body.whisk_shuffle_proof_M_commitment = m
if not IsValidWhiskShuffleProof(
spec.CURDLEPROOFS_CRS,
pre_shuffle_trackers,
empty_block.body.whisk_post_shuffle_trackers,
empty_block.body.whisk_shuffle_proof_M_commitment,
empty_block.body.whisk_shuffle_proof,
):
raise Exception(
"produced shuffle proof is not valid",
pre_shuffle_trackers,
empty_block.body.whisk_post_shuffle_trackers,
empty_block.body.whisk_shuffle_proof_M_commitment,
empty_block.body.whisk_shuffle_proof,
)
# Whisk registration proof
#######
# Branching logic depending if first proposal or not
is_first_proposal = state.validators[proposer_index].whisk_tracker.r_G == spec.BLS_G1_GENERATOR
if is_first_proposal:
# Register new tracker
k_final = whisk_ks_final[proposer_index]
# TODO: Actual logic should pick a random r, but we may want to do something fancy to locate trackers quickly
r = 2
tracker, k_commitment = get_whisk_tracker_and_commitment(k_final, r)
empty_block.body.whisk_registration_proof = GenerateWhiskTrackerProof(tracker, k_final)
empty_block.body.whisk_tracker = tracker
empty_block.body.whisk_k_commitment = k_commitment
else:
# Subsequent proposals, just leave empty
empty_block.body.whisk_registration_proof = spec.WhiskTrackerProof()
empty_block.body.whisk_tracker = spec.WhiskTracker()
empty_block.body.whisk_k_commitment = spec.BLSG1Point()
return empty_block
def build_empty_block_for_next_slot(spec, state):
return build_empty_block(spec, state, state.slot + 1)
def is_whisk_proposer(tracker: WhiskTracker, k: int) -> bool:
return py_ecc_G1_to_bytes48(multiply(py_ecc_bytes48_to_G1(tracker.r_G), k)) == tracker.k_r_G
def get_beacon_proposer_to_build(spec, state, proposer_index=None):
if is_post_whisk(spec):
if proposer_index is None:
return find_whisk_proposer(spec, state)
else:
return proposer_index
else:
return spec.get_beacon_proposer_index(state)
def find_whisk_proposer(spec, state):
raise Exception("proposer not known without heavy math")
proposer_tracker = state.whisk_proposer_trackers[state.slot % spec.WHISK_PROPOSER_TRACKERS_COUNT]
# First attempt direct equality with trackers
for i, validator in enumerate(state.validators):
# # This is insanely slow
# if validator.whisk_tracker == proposer_tracker:
if True:
return i
# In Whisk where to get proposer from?
raise Exception("proposer_tracker not matched")
def build_empty_block_for_next_slot(spec, state, proposer_index=None):
return build_empty_block(spec, state, state.slot + 1, proposer_index)
def get_state_and_beacon_parent_root_at_slot(spec, state, slot):

View File

@ -17,6 +17,7 @@ SHARDING = SpecForkName('sharding')
CUSTODY_GAME = SpecForkName('custody_game')
DAS = SpecForkName('das')
EIP6110 = SpecForkName('eip6110')
WHISK = SpecForkName('whisk')
#
# SpecFork settings
@ -32,11 +33,12 @@ ALL_PHASES = (
DENEB,
# Experimental patches
EIP6110,
WHISK,
)
# The forks that have light client specs
LIGHT_CLIENT_TESTING_FORKS = (*[item for item in MAINNET_FORKS if item != PHASE0], DENEB)
# The forks that output to the test vectors.
TESTGEN_FORKS = (*MAINNET_FORKS, DENEB, EIP6110)
TESTGEN_FORKS = (*MAINNET_FORKS, DENEB, EIP6110, WHISK)
ALL_FORK_UPGRADES = {
# pre_fork_name: post_fork_name

View File

@ -1,10 +1,13 @@
from .constants import (
PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB,
EIP6110,
WHISK,
)
def is_post_fork(a, b):
if a == WHISK:
return b in [PHASE0, ALTAIR, BELLATRIX, CAPELLA, WHISK]
if a == EIP6110:
return b in [PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110]
if a == DENEB:
@ -38,3 +41,7 @@ def is_post_deneb(spec):
def is_post_eip6110(spec):
return is_post_fork(spec.fork, EIP6110)
def is_post_whisk(spec):
return is_post_fork(spec.fork, WHISK)

View File

@ -1,13 +1,13 @@
from eth2spec.test.helpers.constants import (
ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110,
ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110, WHISK,
)
from eth2spec.test.helpers.execution_payload import (
compute_el_header_block_hash,
)
from eth2spec.test.helpers.forks import (
is_post_altair, is_post_bellatrix, is_post_capella, is_post_eip6110,
is_post_altair, is_post_bellatrix, is_post_capella, is_post_eip6110, is_post_whisk,
)
from eth2spec.test.helpers.keys import pubkeys
from eth2spec.test.helpers.keys import pubkeys, whisk_ks_initial
def build_mock_validator(spec, i: int, balance: int):
@ -86,6 +86,9 @@ def create_genesis_state(spec, validator_balances, activation_threshold):
elif spec.fork == EIP6110:
previous_version = spec.config.DENEB_FORK_VERSION
current_version = spec.config.EIP6110_FORK_VERSION
elif spec.fork == WHISK:
previous_version = spec.config.CAPELLA_FORK_VERSION
current_version = spec.config.WHISK_FORK_VERSION
state = spec.BeaconState(
genesis_time=0,

View File

@ -4,3 +4,13 @@ from py_ecc.bls import G2ProofOfPossession as bls
privkeys = [i + 1 for i in range(32 * 256)]
pubkeys = [bls.SkToPk(privkey) for privkey in privkeys]
pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)}
MAX_KEYS = 32 * 256
whisk_ks_initial = [i for i in range(MAX_KEYS)]
# Must be unique among the set `whisk_ks_initial + whisk_ks_final`
whisk_ks_final = [MAX_KEYS + i for i in range(MAX_KEYS)]
known_whisk_trackers = {}
def register_known_whisk_tracker(k_r_G: bytes, index: int):
known_whisk_trackers[k_r_G] = index

View File

@ -0,0 +1,53 @@
from typing import Tuple
from eth_typing import BLSPubkey
from py_ecc.optimized_bls12_381.optimized_curve import G1, multiply
from py_ecc.bls.g2_primitives import (
G1_to_pubkey as py_ecc_G1_to_bytes48,
pubkey_to_G1 as py_ecc_bytes48_to_G1,
)
from curdleproofs import (
GenerateWhiskTrackerProof,
WhiskTracker,
IsValidWhiskOpeningProof,
GenerateWhiskShuffleProof,
IsValidWhiskShuffleProof
)
def get_whisk_k_commitment(k: int) -> BLSPubkey:
return py_ecc_G1_to_bytes48(multiply(G1, int(k)))
def get_whisk_tracker(k: int, r: int) -> WhiskTracker:
r_G = multiply(G1, int(r))
k_r_G = multiply(r_G, int(k))
return WhiskTracker(py_ecc_G1_to_bytes48(r_G), py_ecc_G1_to_bytes48(k_r_G))
def get_whisk_tracker_and_commitment(k: int, r: int) -> Tuple[WhiskTracker, BLSPubkey]:
k_G = multiply(G1, int(k))
r_G = multiply(G1, int(r))
k_r_G = multiply(r_G, int(k))
tracker = WhiskTracker(py_ecc_G1_to_bytes48(r_G), py_ecc_G1_to_bytes48(k_r_G))
commitment = py_ecc_G1_to_bytes48(k_G)
return tracker, commitment
# Trigger condition for first proposal
def set_as_first_proposal(spec, state, proposer_index: int):
# Ensure tracker is empty to prevent breaking it
assert state.validators[proposer_index].whisk_tracker.r_G == spec.BLSG1Point()
state.validators[proposer_index].whisk_tracker.r_G = spec.BLS_G1_GENERATOR
def is_first_proposal(spec, state, proposer_index: int) -> bool:
return state.validators[proposer_index].whisk_tracker.r_G == spec.BLS_G1_GENERATOR
def set_registration(body, k: int, r: int):
tracker, k_commitment = get_whisk_tracker_and_commitment(k, r)
body.whisk_registration_proof = GenerateWhiskTrackerProof(tracker, k)
body.whisk_tracker = tracker
body.whisk_k_commitment = k_commitment
def set_opening_proof(spec, state, block, proposer_index: int, k: int, r: int):
tracker, k_commitment = get_whisk_tracker_and_commitment(k, r)
state.whisk_proposer_trackers[state.slot % spec.WHISK_PROPOSER_TRACKERS_COUNT] = tracker
state.validators[proposer_index].whisk_k_commitment = k_commitment
block.proposer_index = proposer_index
block.body.whisk_opening_proof = GenerateWhiskTrackerProof(tracker, k)

View File

@ -0,0 +1,148 @@
from eth2spec.test.context import spec_state_test, with_whisk_and_later, expect_assertion_error
from eth2spec.test.helpers.keys import whisk_ks_initial
from eth2spec.test.helpers.whisk import get_whisk_tracker
from curdleproofs import GenerateWhiskShuffleProof
def set_correct_shuffle_proofs(spec, state, body):
pre_shuffle_trackers = get_and_populate_pre_shuffle_trackers(spec, state, body)
post_trackers, m, shuffle_proof = GenerateWhiskShuffleProof(spec.CURDLEPROOFS_CRS, pre_shuffle_trackers)
body.whisk_post_shuffle_trackers = post_trackers
body.whisk_shuffle_proof = shuffle_proof
body.whisk_shuffle_proof_M_commitment = m
def get_and_populate_pre_shuffle_trackers(spec, state, body):
shuffle_indices = spec.get_shuffle_indices(body.randao_reveal)
pre_shuffle_trackers = []
for i in shuffle_indices:
# Set r to some value > 1 ( = 2+i)
tracker = get_whisk_tracker(whisk_ks_initial[i], 2 + i)
state.whisk_candidate_trackers[i] = tracker
pre_shuffle_trackers.append(tracker)
return pre_shuffle_trackers
def get_pre_shuffle_trackers(spec, state, body):
return [state.whisk_candidate_trackers[i] for i in spec.get_shuffle_indices(body.randao_reveal)]
def set_state_epoch(spec, state, epoch):
state.slot = epoch * spec.SLOTS_PER_EPOCH
def set_state_epoch_selection_gap(spec, state):
set_state_epoch(spec, state, spec.WHISK_EPOCHS_PER_SHUFFLING_PHASE - 1)
def empty_block_body(spec):
return spec.BeaconBlockBody()
def run_process_shuffled_trackers(spec, state, body, valid=True):
yield 'pre', state
yield 'body', body
if not valid:
expect_assertion_error(lambda: spec.process_shuffled_trackers(state, body))
yield 'post', None
return
spec.process_shuffled_trackers(state, body)
yield 'post', state
@with_whisk_and_later
@spec_state_test
def test_shuffle_trackers(spec, state):
body = empty_block_body(spec)
set_correct_shuffle_proofs(spec, state, body)
yield from run_process_shuffled_trackers(spec, state, body)
@with_whisk_and_later
@spec_state_test
def test_no_shuffle_minus_selection_gap(spec, state):
body = empty_block_body(spec)
set_state_epoch_selection_gap(spec, state)
yield from run_process_shuffled_trackers(spec, state, body)
@with_whisk_and_later
@spec_state_test
def test_no_shuffle_minus_one_and_selection_gap(spec, state):
body = empty_block_body(spec)
set_state_epoch(spec, state, spec.WHISK_EPOCHS_PER_SHUFFLING_PHASE - spec.WHISK_PROPOSER_SELECTION_GAP - 1)
yield from run_process_shuffled_trackers(spec, state, body)
@with_whisk_and_later
@spec_state_test
def test_shuffle_during_selection_gap(spec, state):
body = empty_block_body(spec)
set_correct_shuffle_proofs(spec, state, body)
set_state_epoch_selection_gap(spec, state)
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
# Invalid cases on shuffle
# - wrong m
# - wrong proof
# - wrong post shuffle
@with_whisk_and_later
@spec_state_test
def test_invalid_shuffle_bad_m(spec, state):
body = empty_block_body(spec)
set_correct_shuffle_proofs(spec, state, body)
body.whisk_shuffle_proof_M_commitment = spec.BLSG1Point()
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
@with_whisk_and_later
@spec_state_test
def test_invalid_shuffle_bad_proof(spec, state):
body = empty_block_body(spec)
set_correct_shuffle_proofs(spec, state, body)
body.whisk_shuffle_proof = spec.WhiskShuffleProof()
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
@with_whisk_and_later
@spec_state_test
def test_invalid_shuffle_bad_trackers_zero(spec, state):
body = empty_block_body(spec)
set_correct_shuffle_proofs(spec, state, body)
body.whisk_post_shuffle_trackers[0] = spec.WhiskTracker()
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
# TODO: This test does not fail
# @with_whisk_and_later
# @spec_state_test
# def test_invalid_shuffle_bad_trackers_swap(spec, state):
# body = empty_block_body(spec)
# set_correct_shuffle_proofs(spec, state, body)
# assert body.whisk_post_shuffle_trackers[0] != body.whisk_post_shuffle_trackers[1]
# tracker_0 = body.whisk_post_shuffle_trackers[0]
# body.whisk_post_shuffle_trackers[0] = body.whisk_post_shuffle_trackers[1]
# body.whisk_post_shuffle_trackers[1] = tracker_0
# yield from run_process_shuffled_trackers(spec, state, body, valid=False)
# Invalid things on gap
# - not empty shuffle trackers
# - not empty m
# - not empty proof
@with_whisk_and_later
@spec_state_test
def test_invalid_gap_non_zero_m(spec, state):
body = empty_block_body(spec)
body.whisk_shuffle_proof_M_commitment = spec.BLSG1Point('0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
set_state_epoch_selection_gap(spec, state)
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
@with_whisk_and_later
@spec_state_test
def test_invalid_gap_non_zero_proof(spec, state):
body = empty_block_body(spec)
body.whisk_shuffle_proof = spec.WhiskShuffleProof('0xff')
set_state_epoch_selection_gap(spec, state)
yield from run_process_shuffled_trackers(spec, state, body, valid=False)
@with_whisk_and_later
@spec_state_test
def test_invalid_gap_non_zero_trackers(spec, state):
body = empty_block_body(spec)
body.whisk_post_shuffle_trackers = get_and_populate_pre_shuffle_trackers(spec, state, body)
set_state_epoch_selection_gap(spec, state)
yield from run_process_shuffled_trackers(spec, state, body, valid=False)

View File

@ -0,0 +1,59 @@
from eth2spec.test.context import spec_state_test, with_whisk_and_later, expect_assertion_error
from eth2spec.test.helpers.whisk import (
get_whisk_k_commitment,
get_whisk_tracker,
set_opening_proof
)
def empty_block(spec):
return spec.BeaconBlock()
def run_process_whisk_opening_proof(spec, state, block, valid=True):
yield 'pre', state
yield 'block', block
if not valid:
expect_assertion_error(lambda: spec.process_whisk_opening_proof(state, block))
yield 'post', None
return
spec.process_whisk_opening_proof(state, block)
yield 'post', state
PROPOSER_INDEX = 0
K_OK = 2
K_WRONG = 3
R_OK = 2
R_WRONG = 3
@with_whisk_and_later
@spec_state_test
def test_valid_proof(spec, state):
block = empty_block(spec)
set_opening_proof(spec, state, block, PROPOSER_INDEX, K_OK, R_OK)
run_process_whisk_opening_proof(spec, state, block)
@with_whisk_and_later
@spec_state_test
def test_wrong_commitment(spec, state):
block = empty_block(spec)
set_opening_proof(spec, state, block, PROPOSER_INDEX, K_OK, R_OK)
state.validators[PROPOSER_INDEX].whisk_k_commitment = get_whisk_k_commitment(K_WRONG)
run_process_whisk_opening_proof(spec, state, block, valid=False)
@with_whisk_and_later
@spec_state_test
def test_wrong_tracker_r(spec, state):
block = empty_block(spec)
set_opening_proof(spec, state, block, PROPOSER_INDEX, K_OK, R_OK)
state.whisk_proposer_trackers[state.slot % spec.WHISK_PROPOSER_TRACKERS_COUNT] = get_whisk_tracker(K_OK, R_WRONG)
run_process_whisk_opening_proof(spec, state, block, valid=False)
@with_whisk_and_later
@spec_state_test
def test_wrong_proof(spec, state):
block = empty_block(spec)
set_opening_proof(spec, state, block, PROPOSER_INDEX, K_OK, R_OK)
block.body.whisk_opening_proof = spec.WhiskTrackerProof()
run_process_whisk_opening_proof(spec, state, block, valid=False)

View File

@ -0,0 +1,90 @@
from eth2spec.test.context import spec_state_test, with_whisk_and_later, expect_assertion_error
from eth2spec.test.helpers.keys import whisk_ks_initial, whisk_ks_final
from eth2spec.test.helpers.whisk import get_whisk_tracker, set_as_first_proposal, get_whisk_k_commitment, set_registration
from curdleproofs import GenerateWhiskShuffleProof
def empty_block_body(spec):
return spec.BeaconBlockBody()
def set_as_first_proposal_and_proposer(spec, state, proposer_index):
state.latest_block_header.proposer_index = proposer_index
set_as_first_proposal(spec, state, proposer_index)
def run_process_whisk_registration(spec, state, body, valid=True):
yield 'pre', state
yield 'body', body
if not valid:
expect_assertion_error(lambda: spec.process_whisk_registration(state, body))
yield 'post', None
return
spec.process_whisk_registration(state, body)
yield 'post', state
IDENTITY_R = 1
OTHER_R = 2
PROPOSER_INDEX = 0
OTHER_INDEX = 1
OTHER_K = 2
# First proposal
@with_whisk_and_later
@spec_state_test
def test_first_proposal_ok(spec, state):
body = empty_block_body(spec)
set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX)
set_registration(body, OTHER_K, OTHER_R)
yield from run_process_whisk_registration(spec, state, body)
@with_whisk_and_later
@spec_state_test
def test_first_proposal_indentity_tracker(spec, state):
body = empty_block_body(spec)
set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX)
set_registration(body, OTHER_K, IDENTITY_R)
yield from run_process_whisk_registration(spec, state, body, valid=False)
@with_whisk_and_later
@spec_state_test
def test_first_proposal_non_unique_k_other(spec, state):
body = empty_block_body(spec)
set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX)
state.validators[OTHER_INDEX].whisk_k_commitment = get_whisk_k_commitment(OTHER_K)
set_registration(body, OTHER_K, OTHER_R)
yield from run_process_whisk_registration(spec, state, body, valid=False)
@with_whisk_and_later
@spec_state_test
def test_first_proposal_non_unique_k_self(spec, state):
body = empty_block_body(spec)
set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX)
state.validators[PROPOSER_INDEX].whisk_k_commitment = get_whisk_k_commitment(OTHER_K)
set_registration(body, OTHER_K, OTHER_R)
yield from run_process_whisk_registration(spec, state, body, valid=False)
@with_whisk_and_later
@spec_state_test
def test_first_proposal_invalid_proof(spec, state):
body = empty_block_body(spec)
set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX)
set_registration(body, OTHER_K, OTHER_R)
body.whisk_tracker.k_r_G = spec.BLSG1Point()
yield from run_process_whisk_registration(spec, state, body, valid=False)
# Second proposal
@with_whisk_and_later
@spec_state_test
def test_second_proposal_ok(spec, state):
body = empty_block_body(spec)
yield from run_process_whisk_registration(spec, state, body)
@with_whisk_and_later
@spec_state_test
def test_second_proposal_not_zero(spec, state):
body = empty_block_body(spec)
set_registration(body, OTHER_K, OTHER_R)
yield from run_process_whisk_registration(spec, state, body, valid=False)

View File

@ -0,0 +1 @@
from .test_whisk import * # noqa: F401 F403

View File

@ -0,0 +1,58 @@
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot, build_empty_block
)
from eth2spec.test.context import (
spec_state_test,
with_whisk_and_later,
WHISK,
)
from eth2spec.test.helpers.keys import privkeys, pubkeys, whisk_ks_initial
from eth2spec.test.helpers.state import (
state_transition_and_sign_block
)
from curdleproofs import IsValidWhiskShuffleProof
from eth2spec.test.helpers.whisk import is_first_proposal, get_whisk_tracker_and_commitment, set_as_first_proposal
from curdleproofs import WhiskTracker
known_whisk_trackers = {}
def assign_proposer_at_slot(state, slot: int):
state
def initialize_whisk_full(spec, state):
# TODO: De-duplicate code from whisk/fork.md
for index, validator in enumerate(state.validators):
whisk_k_commitment, whisk_tracker = spec.get_initial_commitments(whisk_ks_initial[index])
validator.whisk_k_commitment = whisk_k_commitment
validator.whisk_tracker = whisk_tracker
# Do a candidate selection followed by a proposer selection so that we have proposers for the upcoming day
# Use an old epoch when selecting candidates so that we don't get the same seed as in the next candidate selection
spec.select_whisk_candidate_trackers(state, spec.Epoch(0))
spec.select_whisk_proposer_trackers(state, spec.Epoch(0))
# Fill candidate trackers with the same tracker so shuffling does not break
def fill_candidate_trackers(spec, state, tracker: WhiskTracker):
for i in range(spec.WHISK_CANDIDATE_TRACKERS_COUNT):
state.whisk_candidate_trackers[i] = tracker
@with_whisk_and_later
@spec_state_test
def test_whisk__process_block_single_initial(spec, state):
assert state.slot == 0
proposer_slot_1 = 0
tracker_slot_1, k_commitment = get_whisk_tracker_and_commitment(whisk_ks_initial[proposer_slot_1], 1)
state.validators[proposer_slot_1].whisk_k_commitment = k_commitment
state.whisk_proposer_trackers[1] = tracker_slot_1
fill_candidate_trackers(spec, state, tracker_slot_1)
# Produce and process a whisk block
yield 'pre', state
block = build_empty_block(spec, state, 1, proposer_slot_1)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state