From c6333393a963dc59a794054173d9a3969a50f686 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Wed, 12 Jan 2022 14:17:37 +0200 Subject: [PATCH 01/16] Introduce consensus code for Whisk --- specs/whisk/beacon-chain.md | 381 ++++++++++++++++++++++++++++++++++++ specs/whisk/fork.md | 101 ++++++++++ 2 files changed, 482 insertions(+) create mode 100644 specs/whisk/beacon-chain.md create mode 100644 specs/whisk/fork.md diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md new file mode 100644 index 0000000000..8218a8ece6 --- /dev/null +++ b/specs/whisk/beacon-chain.md @@ -0,0 +1,381 @@ +### Diagram + +```python +""" + cooldown cooldown + | || | || + | || | || + epoch N N+1 vpvv N+2 vpvv + ----+~~~~~~~~~~~~~~~~~~~~~----+~~~~~~~~~~~~~~~~~~~~~----+- + ^ shuffling ^ shuffling ^ + | | | + | | | + proposer selection proposer selection proposer selection + candidate selection candidate selection candidate selection +""" +``` + +### Constants + +| Name | Value | Description | +| - | - | - | +| `WHISK_CANDIDATE_SET_SIZE` | `uint64(2**14)` (= 16,384) | size of candidate set | +| `WHISK_PROPOSER_SET_SIZE` | `uint64(2**13)` (= 8,192) | size of proposer set | +| `WHISK_SHUFFLE_DURATION` | `Epoch(2**8)` (= 256) | duration of the shuffling phase | +| `WHISK_VALIDATORS_PER_SHUFFLE` | `uint64(2**7)` (= 128) | number of validators shuffled at each step | +| `WHISK_SHUFFLE_STEPS_PER_ROUND` | `uint64(2**7)` (= 128) | feistelshuffle steps needed to complete one pass over all rows | +| `WHISK_PROPOSER_SELECTION_GAP` | `Epoch(2)` | epochs between proposer selection event and start of proposer phase | + +Invariant: The protocol should produce enough proposers to last for an entire shuffling phase: `WHISK_PROPOSER_SET_SIZE = WHISK_SHUFFLE_DURATION * SLOTS_PER_EPOCH` + +| Name | Value | +| - | - | +| `DOMAIN_WHISK_CANDIDATE_SELECTION` | `DomainType('0x07000000')` | +| `DOMAIN_WHISK_SHUFFLE` | `DomainType('0x07100000')` | +| `DOMAIN_WHISK_PROPOSER_SELECTION` | `DomainType('0x07200000')` | + +### Cryptography + +#### BLS + +| Name | SSZ equivalent | Description | +| - | - | - | +| `BLSFrScalar` | `Bytes48` | BLS12-381 Fr scalar | +| `BLSG1Point` | `Bytes48` | point on the G1 group of BLS12-381 | + +Implementations MUST perform subgroup checks when deserializing a `BLSG1Point` and before using it in any of the functions below. + +```python +def BLSG1PointFromAffine(x: int, y: int) -> BLSG1Point +``` + +```python +# Scalar multiplication between scalar in F_r and G1 point +def ScalarMult(BLSFrScalar, BLSG1Point) -> BLSG1Point +``` + +| Name | Value | +| - | - | +| `BLS_G1_GENERATOR_X` | `0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb` | +| `BLS_G1_GENERATOR_Y` | `0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1` | +| `BLS_G1_GENERATOR` | `BLSG1PointFromAffine(BLS_G1_GENERATOR_X, BLS_G1_GENERATOR_Y)` | + +#### Whisk + +```python +def IsValidShuffleProof(proof: ShuffleProof, + pre_state: Sequence[WhiskTracker], + post_state: Sequence[WhiskTracker], + permutation_commitment: BLSG1Point) -> bool +``` + +```python +# Return True if `proof` is a valid discrete log equality proof. +# This translates to verifying a proof of knowledge of `k` s.t. [k_r_G = k*r_G ^ k_G = k*G] +def IsValidDLEQProof(proof: ProofOfOpening, + k_r_G: BLSG1Point, r_G: BLSG1Point, + k_G: BLSG1Point, G: BLSG1Point) -> bool +``` + + +| Name | Value | Description | +| - | - | - | +| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT_X` | `TODO{Depends on CRS of shuffle proof}` | x coordinate of commitment to trivial permutation | +| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT_Y` | `TODO{Depends on CRS of shuffle proof}` | y coordinate of commitment to trivial permutation | +| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT` | `BLSG1PointFromAffine(WHISK_TRIVIAL_PERMUTATION_COMMITMENT_X, WHISK_TRIVIAL_PERMUTATION_COMMITMENT_Y)` | commitment to trivial permutation | + +### Epoch processing + +```python +class WhiskTracker(Container): + """A tracker is a randomized validator commitment""" + r_G: BLSG1Point # r*G + k_r_G: BLSG1Point # k*r*G + +class Validator(Container): + # ... + # The Whisk tracker (r*G, k*r*G) of this validator + whisk_tracker: WhiskTracker # [New in Whisk] + # Commitment com(k)=k*G of this validator + whisk_commitment_k: BLSG1Point # [New in Whisk] + # Permutation commitment + whisk_permutation_commitment: BLSG1Point # [New in Whisk] + +class BeaconState(Container): + # ... + whisk_candidates: Vector[WhiskTracker, WHISK_CANDIDATE_SET_SIZE] # [New in Whisk] + whisk_proposers: Vector[WhiskTracker, WHISK_PROPOSER_SET_SIZE] # [New in Whisk] + # ... + + +def whisk_candidate_selection(state: BeaconState, epoch: Epoch) -> None: + """ + Select candidates from the entire set of validators + """ + + active_validator_indices = get_active_validator_indices(state, epoch) + for i in range(WHISK_CANDIDATE_SET_SIZE): + # Use compute_proposer_index() to do effective-balance-weighted sampling + seed = hash(get_seed(state, epoch, DOMAIN_WHISK_CANDIDATE_SELECTION) + uint_to_bytes(i)) + index = compute_proposer_index(state, active_validator_indices, seed) + + # Register the tracker of this validator + state.whisk_candidates[i] = state.validators[index].whisk_tracker + + +def whisk_proposer_selection(state: BeaconState, epoch: Epoch) -> None: + """ + Select proposers from the candidate set + """ + + # Derive seed using an old epoch so that the proposer set can be predicted in advance + seed = get_seed(state, epoch - WHISK_PROPOSER_SELECTION_GAP, DOMAIN_WHISK_PROPOSER_SELECTION) + + for i in range(WHISK_PROPOSER_SET_SIZE): + index = compute_shuffled_index(uint64(i), uint64(len(state.whisk_candidates)), seed) + state.whisk_proposers[i] = state.whisk_candidates[index] + + +def process_whisk_epoch(state: BeaconState) -> None: + # We select candidates and proposers at the beginning of a new Whisk shuffling phase + next_epoch = Epoch(get_current_epoch(state) + 1) + if next_epoch % WHISK_SHUFFLE_DURATION == 0: + whisk_proposer_selection(state, next_epoch) + whisk_candidate_selection(state, next_epoch) + + +def process_epoch(state: BeaconState) -> None: + # ... + process_whisk_epoch(state) +``` + + +### Block processing + +#### Block header + +```python +class DLEQProof: + # Proof of knowledge to the opening of com(k) and to the opening of a Whisk tracker + # This is a sigma DLEQ that proves knowledge of `k` s.t.: + # - k is the dlog of `com(k) = k*G` + # - k is also the dlog of `k_r_G = k*(r_G)` [Whisk tracker] + T_1: BLSG1Point # Sigma commitment + T_2: BLSG1Point # Sigma commitment + s_1: BLSFrScalar # Sigma response + s_2: BLSFrScalar # Sigma response + + +class BeaconBlock(Container): + # ... + proposer_index: ValidatorIndex + whisk_opening_proof: DLEQProof # [New in Whisk] + # ... + + +def whisk_verify_proposer(state: BeaconState, block: BeaconBlock) -> None: + proposer = state.validators[block.proposer_index] + tracker = state.whisk_proposers[state.slot % WHISK_PROPOSER_SET_SIZE] + + assert whisk.IsValidDLEQProof(block.whisk_opening_proof, + tracker.k_r_G, tracker.r_G, + proposer.whisk_commitment_k, BLS_G1_GENERATOR) + + +def process_block_header(state: BeaconState, block: BeaconBlock) -> None: + # ... + # Verify that proposer index is the correct index + # -- REMOVE -- assert block.proposer_index == get_beacon_proposer_index(state) + whisk_verify_proposer(state, block) + # ... +``` + +#### Shuffle block processing + +```python +class ShuffleProof(Container): + # TODO Include the scalars and group elements of the proof + # This will depend on the final shape of the Whisk proofs + + +class BeaconBlockBody(Container): + # ... + whisk_shuffled_trackers: Vector[WhiskTracker, WHISK_VALIDATORS_PER_SHUFFLE] # [New in Whisk] + whisk_shuffle_proof: ShuffleProof # [New in Whisk] + + whisk_registration_proof: DLEQProof # [New in Whisk] + whisk_tracker: WhiskTracker # [New in Whisk] + whisk_commitment_k: BLSG1Point # [New in Whisk] + whisk_permutation_commitment: BLSG1Point # [New in Whisk] + + +def feistel_encrypt(index: uint64, r: uin64, k: uint64) -> uint64: + """ + Apply `r` Feistel rounds on `index` and return the ciphertext. + """ + def F(x): # F is the bijective non-linear function: F(x) = x^3 (mod k) + return (x ** 3) % k + + # Extract 2D (x,y) coordinates from 1D coordinates + x = index // K + y = index % K + + # Apply needed number of Feistel rounds using x as the left half, and y as the right half + for _ in range(r): + x, y = y, (F(y) + x) % K + + # Convert (x,y) coords back to 1D coords + return x*K + y + + +def get_feistelshuffle_indices(s: uint64, r: uint64, k: uint64) -> Sequence[uint64]: + """ + Return indices that the Feistelshuffle algorithm shuffles in step `s` of round `r` assuming a square matrix of + order `k`. + """ + original_indices = [i + k * (s % k) for i in range(k)] # Indices of row `s % k` + return [feistel_encrypt(index, r) for index in original_indices] + + +def get_shuffle_indices(state: BeaconState, epoch: Epoch) -> Sequence[uint64]: + """ + Return the indices that the Feistelshuffle algorithm will shuffle in this slot + """ + current_feistelshuffle_round = state.slot // WHISK_SHUFFLE_STEPS_PER_ROUND + step_in_round = state.slot % WHISK_SHUFFLE_STEPS_PER_ROUND + return get_feistelshuffle_indices(current_feistelshuffle_round, step_in_round, WHISK_VALIDATORS_PER_SHUFFLE) + + +def whisk_process_shuffled_trackers(state: BeaconState, permutation_commitment: BLSG1Point, + post_shuffle_trackers: Sequence[WhiskTracker], shuffle_proof: ShuffleProof) -> None: + epoch = get_current_epoch(state) + + # We NOP if we are cooling down. Cooldown phase starts on the epoch before the proposer selection event + epoch_in_shuffle_phase = epoch % WHISK_SHUFFLE_DURATION + if epoch_in_shuffle_phase + WHISK_PROPOSER_SELECTION_GAP + 1 >= WHISK_SHUFFLE_DURATION: + return + + # Check the shuffle proof + shuffle_indices = get_shuffle_indices(state, epoch) + pre_shuffle_trackers = [state.whisk_candidates[i] for i in shuffle_indices] + assert whisk.IsValidShuffleProof(shuffle_proof, pre_shuffle_trackers, post_shuffle_trackers, permutation_commitment) + + # Shuffle candidate list based on the received permutation + for i, shuffle_index in enumerate(shuffle_indices): + state.whisk_candidates[shuffle_index] = post_shuffle_trackers[i] + + +def is_commitment_unique(state: BeaconState, commitment_k: BLSG1Point) -> bool: + # Check that no validator has `commitment_k` as their commitment + for validator in state.validators: + if validator.whisk_commitment_k == commitment_k: + return False + + return True + + +def register_commitments_from_block(state: BeaconState, block: BeaconBlock, proposer: Validator) -> None: + """ + Register fresh commitments for this validator + """ + + # Ensure uniqueness of k + assert is_commitment_unique(state, block.body.whisk_commitment) + + # Check that the same k is used both in the tracker and in com(k) + assert whisk.IsValidDLEQProof(block.body.whisk_registration_proof, + block.body.whisk_tracker.k_r_G, block.body.whisk_tracker.r_G, + block.body.whisk_commitment_k, BLS_G1_GENERATOR) + + # Everything is OK: register the commitments + proposer.whisk_commitment_k = block.body.whisk_commitment_k + proposer.whisk_tracker = block.body.whisk_tracker + + +def process_whisk_block(state: BeaconState, block: BeaconBlock) -> None: + proposer = state.validators[block.proposer_index] + + whisk_process_shuffled_trackers(state, proposer.whisk_permutation_commitment, + block.body.whisk_shuffled_trackers, block.body.whisk_shuffle_proof) + + # Allow proposer to register fresh Whisk commitments once + if proposer.whisk_tracker.r_G == WHISK_TRIVIAL_PERMUTATION_COMMITMENT: + assert block.body.whisk_tracker.r_G != WHISK_TRIVIAL_PERMUTATION_COMMITMENT + register_commitments_from_block(state, block, proposer) + else: + # If proposer has already registered commitments, they should be set to zeroes in this block + assert block.body.whisk_registration_proof == DLEQProof() + assert block.body.whisk_tracker == WhiskTracker() + assert block.body.whisk_commitment_k == BLSG1Point() + + # We always register a fresh permutation commitment + proposer.whisk_permutation_commitment = block.body.whisk_permutation_commitment + + +def process_block(state: BeaconState, block: BeaconBlock) -> None: + # ... + process_whisk_block(state, block) # [New in Whisk] +``` + +#### Deposits (new validator registration) + +```python +def get_unique_commitment(state: BeaconState, validator_index: ValidatorIndex) -> BLSFrScalar, BLSG1Point: + # Use try-and-increment to find a unique-but-predictable `k` for this validator + counter = 0 + while True: + # Hash input: validator_index || counter + hashed_counter = hash(uint_to_bytes(validator_index) + uint_to_bytes(counter)) + + k = BLSFrScalar(hashed_counter) + commitment_k = bls.ScalarMult(k, BLS_G1_GENERATOR) + + # Return this commitment if it's unique + if is_commitment_unique(state, commitment_k): + return k, commitment_k + + counter += 1 + + +def whisk_get_initial_commitments(state: BeaconState, index: ValidatorIndex) -> BLSG1Point, WhiskTracker: + # Create trivial k and com(k) + k, commitment_k = get_unique_commitment(state, index) + + # Create trivial tracker (G, k*G) + tracker = WhiskTracker( + r_G=BLS_G1_GENERATOR, + k_r_G=bls.ScalarMult(k, BLS_G1_GENERATOR), + ) + + return commitment_k, tracker + + +def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: + commitment_k, tracker = whisk_get_initial_commitments(state, len(state.validators)) # [New in Whisk] + + return Validator( + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + effective_balance=effective_balance, + whisk_commitment_k=commitment_k, # [New in Whisk] + whisk_tracker=tracker, # [New in Whisk] + whisk_permutation_commitment=WHISK_TRIVIAL_PERMUTATION_COMMITMENT, # [New in Whisk] + ) +``` + +#### `get_beacon_proposer_index()` + +```python +def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: + """ + Return the beacon proposer index at the current slot. + TODO MUST only be called after a block header has been processed for this slot + """ + assert state.latest_block_header.slot == state.slot + + return state.latest_block_header.proposer_index +``` diff --git a/specs/whisk/fork.md b/specs/whisk/fork.md new file mode 100644 index 0000000000..3af557d29d --- /dev/null +++ b/specs/whisk/fork.md @@ -0,0 +1,101 @@ +### Fork + +```python +""" + WHISK_FORK_EPOCH + | cooldown + | | || + v vsvv + --+~~~~~~~~~~~~~~~~~~~~~----+- + shuffling ^ + | + | + proposer selection + candidate selection +""" +``` + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| - | - | +| `WHISK_FORK_VERSION` | `Version('0x02000000')` | +| `WHISK_FORK_EPOCH` | **TBD** | + +## Fork to WHISK + +If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == WHISK_FORK_EPOCH`, an irregular state change is made to upgrade to Whisk. `WHISK_FORK_EPOCH` must be a multiple of `WHISK_RUN_DURATION_IN_EPOCHS`. + +The upgrade occurs after the completion of the inner loop of `process_slots` that sets `state.slot` equal to `WHISK_FORK_EPOCH * SLOTS_PER_EPOCH`. + +This ensures that we drop right into the beginning of the shuffling phase but without `process_whisk_epoch()` triggering for this Whisk run. Hence we handle all the setup ourselves in `upgrade_to_whisk()` below. + +```python +def upgrade_to_whisk(pre: merge.BeaconState) -> BeaconState: + epoch = merge.get_current_epoch(pre) + post = BeaconState( + # Versioning + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=WHISK_FORK_VERSION, + epoch=epoch, + ), + # History + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + # Eth1 + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + # Registry + validators=[], + balances=pre.balances, + # Randomness + randao_mixes=pre.randao_mixes, + # Slashings + slashings=pre.slashings, + # Participation + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + # Finality + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + # Inactivity + inactivity_scores=pre.inactivity_Scores, + ) + + # Initialize all validators with predictable commitments + for val_index, pre_validator in enumerate(pre.validators): + whisk_commitment, whisk_tracker = whisk_get_initial_commitments(val_index) + + post_validator = Validator( + pubkey=pre_validator.pubkey, + withdrawal_credentials=pre_validator.withdrawal_credentials, + effective_balance=pre_validator.effective_balance, + slashed=pre_validator.slashed, + activation_eligibility_epoch=pre_validator.activation_eligibility_epoch, + activation_epoch=pre_validator.activation_epoch, + exit_epoch=pre_validator.exit_epoch, + withdrawable_epoch=pre_validator.withdrawable_epoch, + whisk_commitment=whisk_commitment, + whisk_tracker=whisk_tracker, + ) + post.validators.append(post_validator) + + # 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 + whisk_candidate_selection(post, epoch - WHISK_PROPOSER_SELECTION_GAP - 1) + whisk_proposer_selection(post, epoch) + + # Do a final round of candidate selection. We need it so that we have something to shuffle over the upcoming shuffling phase + whisk_candidate_selection(post, epoch) +``` From a49f5e0b5a5041df76bc5c1f878c805fd1b3cfdb Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 14 Jan 2022 16:39:58 +0000 Subject: [PATCH 02/16] polish, simplify, clean up (~100 fewer lines) @asn-d6: As discussed, I fixed a few bugs along the way but likely also introduced some bugs :) --- specs/whisk/beacon-chain.md | 360 ++++++++++++++---------------------- 1 file changed, 135 insertions(+), 225 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index 8218a8ece6..86179ab740 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -19,20 +19,18 @@ | Name | Value | Description | | - | - | - | -| `WHISK_CANDIDATE_SET_SIZE` | `uint64(2**14)` (= 16,384) | size of candidate set | -| `WHISK_PROPOSER_SET_SIZE` | `uint64(2**13)` (= 8,192) | size of proposer set | -| `WHISK_SHUFFLE_DURATION` | `Epoch(2**8)` (= 256) | duration of the shuffling phase | -| `WHISK_VALIDATORS_PER_SHUFFLE` | `uint64(2**7)` (= 128) | number of validators shuffled at each step | -| `WHISK_SHUFFLE_STEPS_PER_ROUND` | `uint64(2**7)` (= 128) | feistelshuffle steps needed to complete one pass over all rows | -| `WHISK_PROPOSER_SELECTION_GAP` | `Epoch(2)` | epochs between proposer selection event and start of proposer phase | - -Invariant: The protocol should produce enough proposers to last for an entire shuffling phase: `WHISK_PROPOSER_SET_SIZE = WHISK_SHUFFLE_DURATION * SLOTS_PER_EPOCH` +| `WHISK_CANDIDATE_TRACKERS_COUNT` | `uint64(2**14)` (= 16,384) | number of candidate trackers | +| `WHISK_PROPOSER_TRACKERS_COUNT` | `uint64(2**13)` (= 8,192) | number of proposer trackers | +| `WHISK_SHUFFLING_PHASE_DURATION` | `Epoch(2**8)` (= 256) | shuffling phase duration | +| `WHISK_VALIDATORS_PER_SHUFFLE` | `uint64(2**7)` (= 128) | number of validators shuffled per shuffle step | +| `WHISK_SHUFFLE_STEPS_PER_ROUND` | `uint64(2**7)` (= 128) | Feistel permutation steps to complete a pass over all rows | +| `WHISK_PROPOSER_SELECTION_GAP` | `Epoch(2)` | gap between proposer selection and the block proposal phase | | Name | Value | | - | - | -| `DOMAIN_WHISK_CANDIDATE_SELECTION` | `DomainType('0x07000000')` | -| `DOMAIN_WHISK_SHUFFLE` | `DomainType('0x07100000')` | -| `DOMAIN_WHISK_PROPOSER_SELECTION` | `DomainType('0x07200000')` | +| `DOMAIN_WHISK_CANDIDATE_SELECTION` | `DomainType('0x07000000')` | +| `DOMAIN_WHISK_SHUFFLE` | `DomainType('0x07100000')` | +| `DOMAIN_WHISK_PROPOSER_SELECTION` | `DomainType('0x07200000')` | ### Cryptography @@ -40,318 +38,232 @@ Invariant: The protocol should produce enough proposers to last for an entire sh | Name | SSZ equivalent | Description | | - | - | - | -| `BLSFrScalar` | `Bytes48` | BLS12-381 Fr scalar | -| `BLSG1Point` | `Bytes48` | point on the G1 group of BLS12-381 | +| `BLSFrScalar` | `Bytes48` | BLS12-381 Fr scalar | +| `BLSG1Point` | `Bytes48` | BLS12-381 G1 point | -Implementations MUST perform subgroup checks when deserializing a `BLSG1Point` and before using it in any of the functions below. +*Note*: A subgroup check MUST be performed when deserializing a `BLSG1Point` for use in any of the functions below. ```python def BLSG1PointFromAffine(x: int, y: int) -> BLSG1Point -``` -```python -# Scalar multiplication between scalar in F_r and G1 point -def ScalarMult(BLSFrScalar, BLSG1Point) -> BLSG1Point + +def ScalarMultiplication(BLSFrScalar, BLSG1Point) -> BLSG1Point ``` | Name | Value | | - | - | -| `BLS_G1_GENERATOR_X` | `0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb` | -| `BLS_G1_GENERATOR_Y` | `0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1` | -| `BLS_G1_GENERATOR` | `BLSG1PointFromAffine(BLS_G1_GENERATOR_X, BLS_G1_GENERATOR_Y)` | +| `BLS_G1_GENERATOR_X` | `0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb` | +| `BLS_G1_GENERATOR_Y` | `0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1` | +| `BLS_G1_GENERATOR` | `BLSG1PointFromAffine(BLS_G1_GENERATOR_X, BLS_G1_GENERATOR_Y)` | #### Whisk ```python -def IsValidShuffleProof(proof: ShuffleProof, - pre_state: Sequence[WhiskTracker], - post_state: Sequence[WhiskTracker], - permutation_commitment: BLSG1Point) -> bool -``` +class WhiskShuffleProof: + ### TODO -```python -# Return True if `proof` is a valid discrete log equality proof. -# This translates to verifying a proof of knowledge of `k` s.t. [k_r_G = k*r_G ^ k_G = k*G] -def IsValidDLEQProof(proof: ProofOfOpening, - k_r_G: BLSG1Point, r_G: BLSG1Point, - k_G: BLSG1Point, G: BLSG1Point) -> bool -``` +class WhiskTrackerProof: + T_1: BLSG1Point # Sigma commitment + T_2: BLSG1Point # Sigma commitment + s_1: BLSFrScalar # Sigma response + s_2: BLSFrScalar # Sigma response +def IsValidShuffleProof(permutation_commitment: BLSG1Point, + pre_shuffle_trackers: Sequence[WhiskTracker], + post_shuffle_trackers: Sequence[WhiskTracker], + shuffle_proof: WhiskShuffleProof) -> bool + """ + Verify `post_shuffle_trackers` is the permutation of `pre_shuffle_trackers` according to `permutation_commitment`. + """ -| Name | Value | Description | -| - | - | - | -| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT_X` | `TODO{Depends on CRS of shuffle proof}` | x coordinate of commitment to trivial permutation | -| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT_Y` | `TODO{Depends on CRS of shuffle proof}` | y coordinate of commitment to trivial permutation | -| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT` | `BLSG1PointFromAffine(WHISK_TRIVIAL_PERMUTATION_COMMITMENT_X, WHISK_TRIVIAL_PERMUTATION_COMMITMENT_Y)` | commitment to trivial permutation | + +def IsValidTrackerProof(tracker: WhiskTracker, k_commitment: BLSG1Point, tracker_proof: WhiskTrackerProof) -> bool + """ + Verify knowledge of `k` such that `tracker.k_r_G == k * tracker.r_G` and `k_commitment == k * G`. + """ +``` + +| Name | Value | +| - | - | +| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT_X` | `TODO{Depends on CRS of shuffle proof}` | +| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT_Y` | `TODO{Depends on CRS of shuffle proof}` | +| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT` | `BLSG1PointFromAffine(WHISK_TRIVIAL_PERMUTATION_COMMITMENT_X, WHISK_TRIVIAL_PERMUTATION_COMMITMENT_Y)` | ### Epoch processing ```python class WhiskTracker(Container): - """A tracker is a randomized validator commitment""" r_G: BLSG1Point # r*G k_r_G: BLSG1Point # k*r*G class Validator(Container): # ... - # The Whisk tracker (r*G, k*r*G) of this validator - whisk_tracker: WhiskTracker # [New in Whisk] - # Commitment com(k)=k*G of this validator - whisk_commitment_k: BLSG1Point # [New in Whisk] - # Permutation commitment - whisk_permutation_commitment: BLSG1Point # [New in Whisk] + # Whisk + whisk_tracker: WhiskTracker # Whisk tracker (r*G, k*r*G) [New in Whisk] + whisk_k_commitment: BLSG1Point # Whisk k commitment k*G [New in Whisk] + whisk_permutation_commitment: BLSG1Point # Whisk permutation commitment [New in Whisk] class BeaconState(Container): # ... - whisk_candidates: Vector[WhiskTracker, WHISK_CANDIDATE_SET_SIZE] # [New in Whisk] - whisk_proposers: Vector[WhiskTracker, WHISK_PROPOSER_SET_SIZE] # [New in Whisk] - # ... - - -def whisk_candidate_selection(state: BeaconState, epoch: Epoch) -> None: - """ - Select candidates from the entire set of validators - """ - + # Whisk + whisk_candidate_trackers: Vector[WhiskTracker, WHISK_CANDIDATE_TRACKERS_COUNT] # [New in Whisk] + whisk_proposer_trackers: Vector[WhiskTracker, WHISK_PROPOSER_TRACKERS_COUNT] # [New in Whisk] + +def select_whisk_trackers(state: BeaconState, epoch: Epoch) -> None: + # Select proposer trackers from candidate trackers + proposer_seed = get_seed(state, epoch - WHISK_PROPOSER_SELECTION_GAP, DOMAIN_WHISK_PROPOSER_SELECTION) + for i in range(WHISK_PROPOSER_TRACKERS_COUNT): + index = compute_shuffled_index(uint64(i), uint64(len(state.whisk_candidate_trackers)), proposer_seed) + state.whisk_proposer_trackers[i] = state.whisk_candidate_trackers[index] + + # Select candidate trackers of active validators active_validator_indices = get_active_validator_indices(state, epoch) - for i in range(WHISK_CANDIDATE_SET_SIZE): - # Use compute_proposer_index() to do effective-balance-weighted sampling + for i in range(WHISK_CANDIDATE_TRACKERS_COUNT): seed = hash(get_seed(state, epoch, DOMAIN_WHISK_CANDIDATE_SELECTION) + uint_to_bytes(i)) - index = compute_proposer_index(state, active_validator_indices, seed) + validator_index = compute_proposer_index(state, active_validator_indices, seed) # sample by effective balance + state.whisk_candidate_trackers[i] = state.validators[validator_index].whisk_tracker - # Register the tracker of this validator - state.whisk_candidates[i] = state.validators[index].whisk_tracker - -def whisk_proposer_selection(state: BeaconState, epoch: Epoch) -> None: - """ - Select proposers from the candidate set - """ - - # Derive seed using an old epoch so that the proposer set can be predicted in advance - seed = get_seed(state, epoch - WHISK_PROPOSER_SELECTION_GAP, DOMAIN_WHISK_PROPOSER_SELECTION) - - for i in range(WHISK_PROPOSER_SET_SIZE): - index = compute_shuffled_index(uint64(i), uint64(len(state.whisk_candidates)), seed) - state.whisk_proposers[i] = state.whisk_candidates[index] - - -def process_whisk_epoch(state: BeaconState) -> None: - # We select candidates and proposers at the beginning of a new Whisk shuffling phase +def process_whisk_updates(state: BeaconState) -> None: next_epoch = Epoch(get_current_epoch(state) + 1) - if next_epoch % WHISK_SHUFFLE_DURATION == 0: - whisk_proposer_selection(state, next_epoch) - whisk_candidate_selection(state, next_epoch) - + if next_epoch % WHISK_SHUFFLING_PHASE_DURATION == 0: # tracker selection happens at the start of shuffling phases + select_whisk_trackers(state, next_epoch) def process_epoch(state: BeaconState) -> None: # ... - process_whisk_epoch(state) + process_whisk_updates(state) # [New in Whisk] ``` - ### Block processing #### Block header ```python -class DLEQProof: - # Proof of knowledge to the opening of com(k) and to the opening of a Whisk tracker - # This is a sigma DLEQ that proves knowledge of `k` s.t.: - # - k is the dlog of `com(k) = k*G` - # - k is also the dlog of `k_r_G = k*(r_G)` [Whisk tracker] - T_1: BLSG1Point # Sigma commitment - T_2: BLSG1Point # Sigma commitment - s_1: BLSFrScalar # Sigma response - s_2: BLSFrScalar # Sigma response - - class BeaconBlock(Container): # ... proposer_index: ValidatorIndex - whisk_opening_proof: DLEQProof # [New in Whisk] + whisk_opening_proof: WhiskTrackerProof # [New in Whisk] # ... - -def whisk_verify_proposer(state: BeaconState, block: BeaconBlock) -> None: - proposer = state.validators[block.proposer_index] - tracker = state.whisk_proposers[state.slot % WHISK_PROPOSER_SET_SIZE] - - assert whisk.IsValidDLEQProof(block.whisk_opening_proof, - tracker.k_r_G, tracker.r_G, - proposer.whisk_commitment_k, BLS_G1_GENERATOR) +def process_whisk_opening_proof(state: BeaconState, block: BeaconBlock) -> None: + tracker = state.whisk_proposer_trackers[state.slot % WHISK_PROPOSER_TRACKERS_COUNT] + k_commitment = state.validators[block.proposer_index].whisk_k_commitment + assert whisk.IsValidTrackerProof(tracker, k_commitment, block.whisk_opening_proof) def process_block_header(state: BeaconState, block: BeaconBlock) -> None: # ... - # Verify that proposer index is the correct index - # -- REMOVE -- assert block.proposer_index == get_beacon_proposer_index(state) - whisk_verify_proposer(state, block) + # [Removed in Whisk] Verify that proposer index is the correct index + # [Removed in Whisk] assert block.proposer_index == get_beacon_proposer_index(state) + process_whisk_opening_proof(state, block) # [New in Whisk] # ... ``` -#### Shuffle block processing +#### Wisk ```python -class ShuffleProof(Container): - # TODO Include the scalars and group elements of the proof - # This will depend on the final shape of the Whisk proofs - - class BeaconBlockBody(Container): # ... - whisk_shuffled_trackers: Vector[WhiskTracker, WHISK_VALIDATORS_PER_SHUFFLE] # [New in Whisk] + whisk_post_shuffle_trackers: Vector[WhiskTracker, WHISK_VALIDATORS_PER_SHUFFLE] # [New in Whisk] whisk_shuffle_proof: ShuffleProof # [New in Whisk] - whisk_registration_proof: DLEQProof # [New in Whisk] + whisk_registration_proof: WhiskTrackerProof # [New in Whisk] whisk_tracker: WhiskTracker # [New in Whisk] - whisk_commitment_k: BLSG1Point # [New in Whisk] + whisk_k_commitment: BLSG1Point # [New in Whisk] whisk_permutation_commitment: BLSG1Point # [New in Whisk] -def feistel_encrypt(index: uint64, r: uin64, k: uint64) -> uint64: - """ - Apply `r` Feistel rounds on `index` and return the ciphertext. - """ - def F(x): # F is the bijective non-linear function: F(x) = x^3 (mod k) - return (x ** 3) % k - - # Extract 2D (x,y) coordinates from 1D coordinates - x = index // K - y = index % K +def get_feistel_encryption(index: uint64, rounds: uin64, K: uint64) -> uint64: + def F(x): # F(x) = x^3 (mod K) is a bijective non-linear function + return (x ** 3) % K - # Apply needed number of Feistel rounds using x as the left half, and y as the right half - for _ in range(r): + # Convert 2D (x, y) coordinates from 1D coordinates, apply Fiestel rounds, and convert back to 1D coordinates + x, y = index // K, index % K + for _ in range(rounds): x, y = y, (F(y) + x) % K - - # Convert (x,y) coords back to 1D coords - return x*K + y - - -def get_feistelshuffle_indices(s: uint64, r: uint64, k: uint64) -> Sequence[uint64]: - """ - Return indices that the Feistelshuffle algorithm shuffles in step `s` of round `r` assuming a square matrix of - order `k`. - """ - original_indices = [i + k * (s % k) for i in range(k)] # Indices of row `s % k` - return [feistel_encrypt(index, r) for index in original_indices] + return x * K + y def get_shuffle_indices(state: BeaconState, epoch: Epoch) -> Sequence[uint64]: """ - Return the indices that the Feistelshuffle algorithm will shuffle in this slot + Return the indices that the Feistel permutation shuffles in this slot. """ - current_feistelshuffle_round = state.slot // WHISK_SHUFFLE_STEPS_PER_ROUND - step_in_round = state.slot % WHISK_SHUFFLE_STEPS_PER_ROUND - return get_feistelshuffle_indices(current_feistelshuffle_round, step_in_round, WHISK_VALIDATORS_PER_SHUFFLE) + shuffle_round = state.slot // WHISK_SHUFFLE_STEPS_PER_ROUND + shuffle_step = state.slot % WHISK_SHUFFLE_STEPS_PER_ROUND + row_indices = [i + WHISK_VALIDATORS_PER_SHUFFLE * shuffle_step for i in range(WHISK_VALIDATORS_PER_SHUFFLE)] + return [get_feistel_encryption(index, shuffle_round, WHISK_VALIDATORS_PER_SHUFFLE) for index in row_indices] -def whisk_process_shuffled_trackers(state: BeaconState, permutation_commitment: BLSG1Point, - post_shuffle_trackers: Sequence[WhiskTracker], shuffle_proof: ShuffleProof) -> None: +def whisk_process_shuffled_trackers(state: BeaconState, body: BeaconBlockBody) -> None: epoch = get_current_epoch(state) - - # We NOP if we are cooling down. Cooldown phase starts on the epoch before the proposer selection event - epoch_in_shuffle_phase = epoch % WHISK_SHUFFLE_DURATION - if epoch_in_shuffle_phase + WHISK_PROPOSER_SELECTION_GAP + 1 >= WHISK_SHUFFLE_DURATION: - return + epoch_in_shuffle_phase = epoch % WHISK_SHUFFLING_PHASE_DURATION + if epoch_in_shuffle_phase + WHISK_PROPOSER_SELECTION_GAP + 1 >= WHISK_SHUFFLING_PHASE_DURATION: + permutation_commitment = WHISK_TRIVIAL_PERMUTATION_COMMITMENT # Require the trivial permutation during cooldown + else: + permutation_commitment = state.validators[get_beacon_proposer_index(state)].permutation_commitment # Check the shuffle proof shuffle_indices = get_shuffle_indices(state, epoch) - pre_shuffle_trackers = [state.whisk_candidates[i] for i in shuffle_indices] - assert whisk.IsValidShuffleProof(shuffle_proof, pre_shuffle_trackers, post_shuffle_trackers, permutation_commitment) + pre_shuffle_trackers = [state.whisk_candidate_trackers[i] for i in shuffle_indices] + post_shuffle_trackers = body.whisk_post_shuffle_trackers + shuffle_proof = body.whisk_shuffle_proof + assert whisk.IsValidShuffleProof(permutation_commitment, pre_shuffle_trackers, post_shuffle_trackers, shuffle_proof) - # Shuffle candidate list based on the received permutation + # Shuffle candidate trackers for i, shuffle_index in enumerate(shuffle_indices): - state.whisk_candidates[shuffle_index] = post_shuffle_trackers[i] + state.whisk_candidate_trackers[shuffle_index] = post_shuffle_trackers[i] -def is_commitment_unique(state: BeaconState, commitment_k: BLSG1Point) -> bool: - # Check that no validator has `commitment_k` as their commitment - for validator in state.validators: - if validator.whisk_commitment_k == commitment_k: - return False +def is_k_commitment_unique(state: BeaconState, k_commitment: BLSG1Point) -> bool: + return not any([validator.whisk_k_commitment == k_commitment for validator in state.validators]) - return True - -def register_commitments_from_block(state: BeaconState, block: BeaconBlock, proposer: Validator) -> None: - """ - Register fresh commitments for this validator - """ - - # Ensure uniqueness of k - assert is_commitment_unique(state, block.body.whisk_commitment) - - # Check that the same k is used both in the tracker and in com(k) - assert whisk.IsValidDLEQProof(block.body.whisk_registration_proof, - block.body.whisk_tracker.k_r_G, block.body.whisk_tracker.r_G, - block.body.whisk_commitment_k, BLS_G1_GENERATOR) - - # Everything is OK: register the commitments - proposer.whisk_commitment_k = block.body.whisk_commitment_k - proposer.whisk_tracker = block.body.whisk_tracker - - -def process_whisk_block(state: BeaconState, block: BeaconBlock) -> None: - proposer = state.validators[block.proposer_index] - - whisk_process_shuffled_trackers(state, proposer.whisk_permutation_commitment, - block.body.whisk_shuffled_trackers, block.body.whisk_shuffle_proof) +def process_whisk(state: BeaconState, body: BeaconBlockBody) -> None: + whisk_process_shuffled_trackers(state, body) # Allow proposer to register fresh Whisk commitments once - if proposer.whisk_tracker.r_G == WHISK_TRIVIAL_PERMUTATION_COMMITMENT: - assert block.body.whisk_tracker.r_G != WHISK_TRIVIAL_PERMUTATION_COMMITMENT - register_commitments_from_block(state, block, proposer) + proposer = state.validators[get_beacon_proposer_index(state)] + if proposer.whisk_tracker.r_G == BLS_G1_GENERATOR: + assert body.whisk_tracker.r_G != BLS_G1_GENERATOR + # Check k is unique and that the same k is used in both the tracker and k commitment + assert is_k_commitment_unique(state, body.whisk_k_commitment) + assert whisk.IsValidTrackerProof(body.wisk_tracker, body.whisk_k_commitment, body.whisk_registration_proof) + + proposer.whisk_tracker = body.whisk_tracker + proposer.whisk_k_commitment = body.whisk_k_commitment else: - # If proposer has already registered commitments, they should be set to zeroes in this block - assert block.body.whisk_registration_proof == DLEQProof() - assert block.body.whisk_tracker == WhiskTracker() - assert block.body.whisk_commitment_k == BLSG1Point() + assert body.whisk_registration_proof == WhiskTrackerProof() + assert body.whisk_tracker == WhiskTracker() + assert body.whisk_k_commitment == BLSG1Point() - # We always register a fresh permutation commitment - proposer.whisk_permutation_commitment = block.body.whisk_permutation_commitment + # Always register a fresh permutation commitment + proposer.whisk_permutation_commitment = body.whisk_permutation_commitment def process_block(state: BeaconState, block: BeaconBlock) -> None: # ... - process_whisk_block(state, block) # [New in Whisk] + process_whisk(state, block.body) # [New in Whisk] ``` -#### Deposits (new validator registration) +#### Deposits ```python -def get_unique_commitment(state: BeaconState, validator_index: ValidatorIndex) -> BLSFrScalar, BLSG1Point: - # Use try-and-increment to find a unique-but-predictable `k` for this validator +def get_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSFrScalar: + """ + Find a unique dummy `k` using try-and-increment. + """ counter = 0 while True: - # Hash input: validator_index || counter - hashed_counter = hash(uint_to_bytes(validator_index) + uint_to_bytes(counter)) - - k = BLSFrScalar(hashed_counter) - commitment_k = bls.ScalarMult(k, BLS_G1_GENERATOR) - - # Return this commitment if it's unique - if is_commitment_unique(state, commitment_k): - return k, commitment_k - + k = BLSFrScalar(hash(uint_to_bytes(validator_index) + uint_to_bytes(counter))) # hash `validator_index || counter` + if is_k_commitment_unique(state, bls.ScalarMultiplication(k, BLS_G1_GENERATOR)): + return k counter += 1 -def whisk_get_initial_commitments(state: BeaconState, index: ValidatorIndex) -> BLSG1Point, WhiskTracker: - # Create trivial k and com(k) - k, commitment_k = get_unique_commitment(state, index) - - # Create trivial tracker (G, k*G) - tracker = WhiskTracker( - r_G=BLS_G1_GENERATOR, - k_r_G=bls.ScalarMult(k, BLS_G1_GENERATOR), - ) - - return commitment_k, tracker - - def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: - commitment_k, tracker = whisk_get_initial_commitments(state, len(state.validators)) # [New in Whisk] + amount = deposit.data.amount + effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + k = get_whisk_k(state, len(state.validators)) # [New in Whisk] return Validator( pubkey=deposit.data.pubkey, @@ -361,8 +273,8 @@ def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validato exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, effective_balance=effective_balance, - whisk_commitment_k=commitment_k, # [New in Whisk] - whisk_tracker=tracker, # [New in Whisk] + whisk_tracker=WhiskTracker(BLS_G1_GENERATOR, bls.ScalarMultiplication(k, BLS_G1_GENERATOR)), # [New in Whisk] + whisk_k_commitment=bls.ScalarMultiplication(k, BLS_G1_GENERATOR), # [New in Whisk] whisk_permutation_commitment=WHISK_TRIVIAL_PERMUTATION_COMMITMENT, # [New in Whisk] ) ``` @@ -373,9 +285,7 @@ def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validato def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: """ Return the beacon proposer index at the current slot. - TODO MUST only be called after a block header has been processed for this slot """ - assert state.latest_block_header.slot == state.slot - + assert state.latest_block_header.slot == state.slot # sanity check `process_block_header` has been called return state.latest_block_header.proposer_index ``` From c96895c928501fa6dbc846363e644af80133b036 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 18 Jan 2022 17:38:26 +0000 Subject: [PATCH 03/16] minor cleanups and fixes --- specs/whisk/beacon-chain.md | 67 +++++++++++++------------------------ 1 file changed, 23 insertions(+), 44 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index 86179ab740..066b022e16 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -21,7 +21,7 @@ | - | - | - | | `WHISK_CANDIDATE_TRACKERS_COUNT` | `uint64(2**14)` (= 16,384) | number of candidate trackers | | `WHISK_PROPOSER_TRACKERS_COUNT` | `uint64(2**13)` (= 8,192) | number of proposer trackers | -| `WHISK_SHUFFLING_PHASE_DURATION` | `Epoch(2**8)` (= 256) | shuffling phase duration | +| `WHISK_EPOCHS_PER_SHUFFLING_PHASE` | `Epoch(2**8)` (= 256) | epochs per shuffling phase | | `WHISK_VALIDATORS_PER_SHUFFLE` | `uint64(2**7)` (= 128) | number of validators shuffled per shuffle step | | `WHISK_SHUFFLE_STEPS_PER_ROUND` | `uint64(2**7)` (= 128) | Feistel permutation steps to complete a pass over all rows | | `WHISK_PROPOSER_SELECTION_GAP` | `Epoch(2)` | gap between proposer selection and the block proposal phase | @@ -71,13 +71,13 @@ class WhiskTrackerProof: def IsValidShuffleProof(permutation_commitment: BLSG1Point, pre_shuffle_trackers: Sequence[WhiskTracker], post_shuffle_trackers: Sequence[WhiskTracker], - shuffle_proof: WhiskShuffleProof) -> bool + shuffle_proof: WhiskShuffleProof) -> bool: """ Verify `post_shuffle_trackers` is the permutation of `pre_shuffle_trackers` according to `permutation_commitment`. """ -def IsValidTrackerProof(tracker: WhiskTracker, k_commitment: BLSG1Point, tracker_proof: WhiskTrackerProof) -> bool +def IsValidTrackerProof(tracker: WhiskTracker, k_commitment: BLSG1Point, tracker_proof: WhiskTrackerProof) -> bool: """ Verify knowledge of `k` such that `tracker.k_r_G == k * tracker.r_G` and `k_commitment == k * G`. """ @@ -126,7 +126,7 @@ def select_whisk_trackers(state: BeaconState, epoch: Epoch) -> None: def process_whisk_updates(state: BeaconState) -> None: next_epoch = Epoch(get_current_epoch(state) + 1) - if next_epoch % WHISK_SHUFFLING_PHASE_DURATION == 0: # tracker selection happens at the start of shuffling phases + if next_epoch % WHISK_EPOCHS_PER_SHUFFLING_PHASE == 0: # select trackers at the start of shuffling phases select_whisk_trackers(state, next_epoch) def process_epoch(state: BeaconState) -> None: @@ -164,24 +164,22 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None: ```python class BeaconBlockBody(Container): # ... + # Whisk whisk_post_shuffle_trackers: Vector[WhiskTracker, WHISK_VALIDATORS_PER_SHUFFLE] # [New in Whisk] - whisk_shuffle_proof: ShuffleProof # [New in Whisk] - + whisk_shuffle_proof: WhiskShuffleProof # [New in Whisk] whisk_registration_proof: WhiskTrackerProof # [New in Whisk] whisk_tracker: WhiskTracker # [New in Whisk] whisk_k_commitment: BLSG1Point # [New in Whisk] whisk_permutation_commitment: BLSG1Point # [New in Whisk] - def get_feistel_encryption(index: uint64, rounds: uin64, K: uint64) -> uint64: def F(x): # F(x) = x^3 (mod K) is a bijective non-linear function return (x ** 3) % K - # Convert 2D (x, y) coordinates from 1D coordinates, apply Fiestel rounds, and convert back to 1D coordinates - x, y = index // K, index % K - for _ in range(rounds): + x, y = index // K, index % K # Convert 2D (x, y) coordinates from 1D coordinates + for _ in range(rounds): # Apply Fiestel rounds x, y = y, (F(y) + x) % K - return x * K + y + return x * K + y # Convert back to 1D coordinates def get_shuffle_indices(state: BeaconState, epoch: Epoch) -> Sequence[uint64]: @@ -195,15 +193,14 @@ def get_shuffle_indices(state: BeaconState, epoch: Epoch) -> Sequence[uint64]: def whisk_process_shuffled_trackers(state: BeaconState, body: BeaconBlockBody) -> None: - epoch = get_current_epoch(state) - epoch_in_shuffle_phase = epoch % WHISK_SHUFFLING_PHASE_DURATION - if epoch_in_shuffle_phase + WHISK_PROPOSER_SELECTION_GAP + 1 >= WHISK_SHUFFLING_PHASE_DURATION: + epoch_in_shuffling_phase = get_current_epoch(state) % WHISK_EPOCHS_PER_SHUFFLING_PHASE + if epoch_in_shuffling_phase + WHISK_PROPOSER_SELECTION_GAP + 1 >= WHISK_EPOCHS_PER_SHUFFLING_PHASE: permutation_commitment = WHISK_TRIVIAL_PERMUTATION_COMMITMENT # Require the trivial permutation during cooldown else: permutation_commitment = state.validators[get_beacon_proposer_index(state)].permutation_commitment # Check the shuffle proof - shuffle_indices = get_shuffle_indices(state, epoch) + shuffle_indices = get_shuffle_indices(state, get_current_epoch(state)) pre_shuffle_trackers = [state.whisk_candidate_trackers[i] for i in shuffle_indices] post_shuffle_trackers = body.whisk_post_shuffle_trackers shuffle_proof = body.whisk_shuffle_proof @@ -221,22 +218,18 @@ def is_k_commitment_unique(state: BeaconState, k_commitment: BLSG1Point) -> bool def process_whisk(state: BeaconState, body: BeaconBlockBody) -> None: whisk_process_shuffled_trackers(state, body) - # Allow proposer to register fresh Whisk commitments once + # Overwrite all validator Whisk fields (first Whisk proposal) or just the permutation commitment (next proposals) proposer = state.validators[get_beacon_proposer_index(state)] - if proposer.whisk_tracker.r_G == BLS_G1_GENERATOR: + if proposer.whisk_tracker.r_G == BLS_G1_GENERATOR: # first Whisk proposal assert body.whisk_tracker.r_G != BLS_G1_GENERATOR - # Check k is unique and that the same k is used in both the tracker and k commitment assert is_k_commitment_unique(state, body.whisk_k_commitment) - assert whisk.IsValidTrackerProof(body.wisk_tracker, body.whisk_k_commitment, body.whisk_registration_proof) - + assert whisk.IsValidTrackerProof(body.whisk_tracker, body.whisk_k_commitment, body.whisk_registration_proof) proposer.whisk_tracker = body.whisk_tracker proposer.whisk_k_commitment = body.whisk_k_commitment - else: + else: # next Whisk proposals assert body.whisk_registration_proof == WhiskTrackerProof() assert body.whisk_tracker == WhiskTracker() assert body.whisk_k_commitment == BLSG1Point() - - # Always register a fresh permutation commitment proposer.whisk_permutation_commitment = body.whisk_permutation_commitment @@ -249,37 +242,23 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: ```python def get_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSFrScalar: - """ - Find a unique dummy `k` using try-and-increment. - """ counter = 0 while True: k = BLSFrScalar(hash(uint_to_bytes(validator_index) + uint_to_bytes(counter))) # hash `validator_index || counter` if is_k_commitment_unique(state, bls.ScalarMultiplication(k, BLS_G1_GENERATOR)): - return k + return k # unique by trial and error counter += 1 def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: - amount = deposit.data.amount - effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) - k = get_whisk_k(state, len(state.validators)) # [New in Whisk] - - return Validator( - pubkey=deposit.data.pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, - activation_eligibility_epoch=FAR_FUTURE_EPOCH, - activation_epoch=FAR_FUTURE_EPOCH, - exit_epoch=FAR_FUTURE_EPOCH, - withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=effective_balance, - whisk_tracker=WhiskTracker(BLS_G1_GENERATOR, bls.ScalarMultiplication(k, BLS_G1_GENERATOR)), # [New in Whisk] - whisk_k_commitment=bls.ScalarMultiplication(k, BLS_G1_GENERATOR), # [New in Whisk] - whisk_permutation_commitment=WHISK_TRIVIAL_PERMUTATION_COMMITMENT, # [New in Whisk] - ) + validator = bellatrix.get_validator_from_deposit() + validator.whisk_tracker = WhiskTracker(BLS_G1_GENERATOR, bls.ScalarMultiplication(k, BLS_G1_GENERATOR)) + validator.whisk_k_commitment = bls.ScalarMultiplication(k, BLS_G1_GENERATOR) + validator.whisk_permutation_commitment = WHISK_TRIVIAL_PERMUTATION_COMMITMENT + return validator ``` -#### `get_beacon_proposer_index()` +#### `get_beacon_proposer_index` ```python def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: From c52e60f0740f7a208ec8e239290caf6dc4b6b71b Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 18 Jan 2022 17:42:06 +0000 Subject: [PATCH 04/16] simplify is_k_commitment_unique --- specs/whisk/beacon-chain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index 066b022e16..1088fa89f8 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -212,7 +212,7 @@ def whisk_process_shuffled_trackers(state: BeaconState, body: BeaconBlockBody) - def is_k_commitment_unique(state: BeaconState, k_commitment: BLSG1Point) -> bool: - return not any([validator.whisk_k_commitment == k_commitment for validator in state.validators]) + return all([validator.whisk_k_commitment != k_commitment for validator in state.validators]) def process_whisk(state: BeaconState, body: BeaconBlockBody) -> None: From 5f364132be09f843f49927314bb3d40af837a31b Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 18 Jan 2022 17:50:15 +0000 Subject: [PATCH 05/16] Update beacon-chain.md --- specs/whisk/beacon-chain.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index 1088fa89f8..2a4cb64eba 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -116,12 +116,12 @@ def select_whisk_trackers(state: BeaconState, epoch: Epoch) -> None: index = compute_shuffled_index(uint64(i), uint64(len(state.whisk_candidate_trackers)), proposer_seed) state.whisk_proposer_trackers[i] = state.whisk_candidate_trackers[index] - # Select candidate trackers of active validators + # Select candidate trackers from active validator trackers active_validator_indices = get_active_validator_indices(state, epoch) for i in range(WHISK_CANDIDATE_TRACKERS_COUNT): seed = hash(get_seed(state, epoch, DOMAIN_WHISK_CANDIDATE_SELECTION) + uint_to_bytes(i)) - validator_index = compute_proposer_index(state, active_validator_indices, seed) # sample by effective balance - state.whisk_candidate_trackers[i] = state.validators[validator_index].whisk_tracker + candidate_index = compute_proposer_index(state, active_validator_indices, seed) # sample by effective balance + state.whisk_candidate_trackers[i] = state.validators[candidate_index].whisk_tracker def process_whisk_updates(state: BeaconState) -> None: @@ -251,7 +251,7 @@ def get_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSFrSca def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: - validator = bellatrix.get_validator_from_deposit() + validator = bellatrix.get_validator_from_deposit(state, deposit) validator.whisk_tracker = WhiskTracker(BLS_G1_GENERATOR, bls.ScalarMultiplication(k, BLS_G1_GENERATOR)) validator.whisk_k_commitment = bls.ScalarMultiplication(k, BLS_G1_GENERATOR) validator.whisk_permutation_commitment = WHISK_TRIVIAL_PERMUTATION_COMMITMENT From 6f2a16ebcf91744b4588f4ce95df431cb1a88871 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 18 Jan 2022 17:59:07 +0000 Subject: [PATCH 06/16] Update beacon-chain.md --- specs/whisk/beacon-chain.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index 2a4cb64eba..e8f1d8e061 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -1,7 +1,6 @@ ### Diagram -```python -""" +``` cooldown cooldown | || | || | || | || @@ -12,7 +11,6 @@ | | | proposer selection proposer selection proposer selection candidate selection candidate selection candidate selection -""" ``` ### Constants @@ -159,7 +157,7 @@ def process_block_header(state: BeaconState, block: BeaconBlock) -> None: # ... ``` -#### Wisk +#### Whisk ```python class BeaconBlockBody(Container): From 2a20c8b42e702a0ccd7088bdd6b35a9235e8b4f5 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Mon, 24 Jan 2022 02:46:11 +0300 Subject: [PATCH 07/16] Initialize `k` in `get_validator_from_deposit()` --- specs/whisk/beacon-chain.md | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index e8f1d8e061..07a6edd6d2 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -1,18 +1,3 @@ -### Diagram - -``` - cooldown cooldown - | || | || - | || | || - epoch N N+1 vpvv N+2 vpvv - ----+~~~~~~~~~~~~~~~~~~~~~----+~~~~~~~~~~~~~~~~~~~~~----+- - ^ shuffling ^ shuffling ^ - | | | - | | | - proposer selection proposer selection proposer selection - candidate selection candidate selection candidate selection -``` - ### Constants | Name | Value | Description | @@ -127,6 +112,7 @@ def process_whisk_updates(state: BeaconState) -> None: if next_epoch % WHISK_EPOCHS_PER_SHUFFLING_PHASE == 0: # select trackers at the start of shuffling phases select_whisk_trackers(state, next_epoch) + def process_epoch(state: BeaconState) -> None: # ... process_whisk_updates(state) # [New in Whisk] @@ -175,7 +161,7 @@ def get_feistel_encryption(index: uint64, rounds: uin64, K: uint64) -> uint64: return (x ** 3) % K x, y = index // K, index % K # Convert 2D (x, y) coordinates from 1D coordinates - for _ in range(rounds): # Apply Fiestel rounds + for _ in range(rounds): # Apply Feistel rounds x, y = y, (F(y) + x) % K return x * K + y # Convert back to 1D coordinates @@ -250,6 +236,7 @@ def get_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSFrSca def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: validator = bellatrix.get_validator_from_deposit(state, deposit) + k = get_whisk_k(state, len(state.validators)) validator.whisk_tracker = WhiskTracker(BLS_G1_GENERATOR, bls.ScalarMultiplication(k, BLS_G1_GENERATOR)) validator.whisk_k_commitment = bls.ScalarMultiplication(k, BLS_G1_GENERATOR) validator.whisk_permutation_commitment = WHISK_TRIVIAL_PERMUTATION_COMMITMENT From cc09d17484ff6dd3eed9804deb4c408d13cb1d9f Mon Sep 17 00:00:00 2001 From: Justin Date: Mon, 31 Jan 2022 19:32:40 +0000 Subject: [PATCH 08/16] minor cleanups --- specs/whisk/beacon-chain.md | 46 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index 07a6edd6d2..4bb46de73b 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -21,7 +21,7 @@ | Name | SSZ equivalent | Description | | - | - | - | -| `BLSFrScalar` | `Bytes48` | BLS12-381 Fr scalar | +| `BLSG1Scalar` | `Bytes48` | BLS12-381 G1 scalar | | `BLSG1Point` | `Bytes48` | BLS12-381 G1 point | *Note*: A subgroup check MUST be performed when deserializing a `BLSG1Point` for use in any of the functions below. @@ -30,7 +30,7 @@ def BLSG1PointFromAffine(x: int, y: int) -> BLSG1Point -def ScalarMultiplication(BLSFrScalar, BLSG1Point) -> BLSG1Point +def BLSG1ScalarMultiply(scalar: BLSG1Scalar, point: BLSG1Point) -> BLSG1Point ``` | Name | Value | @@ -48,8 +48,8 @@ class WhiskShuffleProof: class WhiskTrackerProof: T_1: BLSG1Point # Sigma commitment T_2: BLSG1Point # Sigma commitment - s_1: BLSFrScalar # Sigma response - s_2: BLSFrScalar # Sigma response + s_1: BLSG1Scalar # Sigma response + s_2: BLSG1Scalar # Sigma response def IsValidShuffleProof(permutation_commitment: BLSG1Point, pre_shuffle_trackers: Sequence[WhiskTracker], @@ -62,28 +62,28 @@ def IsValidShuffleProof(permutation_commitment: BLSG1Point, def IsValidTrackerProof(tracker: WhiskTracker, k_commitment: BLSG1Point, tracker_proof: WhiskTrackerProof) -> bool: """ - Verify knowledge of `k` such that `tracker.k_r_G == k * tracker.r_G` and `k_commitment == k * G`. + Verify knowledge of `k` such that `tracker.k_r_G == k * tracker.r_G` and `k_commitment == k * BLS_G1_GENERATOR`. """ ``` | Name | Value | | - | - | -| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT_X` | `TODO{Depends on CRS of shuffle proof}` | -| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT_Y` | `TODO{Depends on CRS of shuffle proof}` | +| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT_X` | `TODO (depends on the shuffle proof CRS)` | +| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT_Y` | `TODO (depends on the shuffle proof CRS)` | | `WHISK_TRIVIAL_PERMUTATION_COMMITMENT` | `BLSG1PointFromAffine(WHISK_TRIVIAL_PERMUTATION_COMMITMENT_X, WHISK_TRIVIAL_PERMUTATION_COMMITMENT_Y)` | ### Epoch processing ```python class WhiskTracker(Container): - r_G: BLSG1Point # r*G - k_r_G: BLSG1Point # k*r*G + r_G: BLSG1Point # r * G + k_r_G: BLSG1Point # k * r * G class Validator(Container): # ... # Whisk - whisk_tracker: WhiskTracker # Whisk tracker (r*G, k*r*G) [New in Whisk] - whisk_k_commitment: BLSG1Point # Whisk k commitment k*G [New in Whisk] + whisk_tracker: WhiskTracker # Whisk tracker (r * G, k * r * G) [New in Whisk] + whisk_k_commitment: BLSG1Point # Whisk k commitment k * BLS_G1_GENERATOR [New in Whisk] whisk_permutation_commitment: BLSG1Point # Whisk permutation commitment [New in Whisk] class BeaconState(Container): @@ -157,13 +157,13 @@ class BeaconBlockBody(Container): whisk_permutation_commitment: BLSG1Point # [New in Whisk] def get_feistel_encryption(index: uint64, rounds: uin64, K: uint64) -> uint64: - def F(x): # F(x) = x^3 (mod K) is a bijective non-linear function - return (x ** 3) % K + def F(x): + return (x ** 3) % K # F(x) = x^3 (mod K) is a bijective non-linear function - x, y = index // K, index % K # Convert 2D (x, y) coordinates from 1D coordinates + x, y = index // K, index % K # Compute 2D coordinates (x, y) from 1D coordinates for _ in range(rounds): # Apply Feistel rounds x, y = y, (F(y) + x) % K - return x * K + y # Convert back to 1D coordinates + return x * K + y # Convert 2D coordinates (x, y) back to 1D coordinates def get_shuffle_indices(state: BeaconState, epoch: Epoch) -> Sequence[uint64]: @@ -177,8 +177,8 @@ def get_shuffle_indices(state: BeaconState, epoch: Epoch) -> Sequence[uint64]: def whisk_process_shuffled_trackers(state: BeaconState, body: BeaconBlockBody) -> None: - epoch_in_shuffling_phase = get_current_epoch(state) % WHISK_EPOCHS_PER_SHUFFLING_PHASE - if epoch_in_shuffling_phase + WHISK_PROPOSER_SELECTION_GAP + 1 >= WHISK_EPOCHS_PER_SHUFFLING_PHASE: + shuffle_epoch = get_current_epoch(state) % WHISK_EPOCHS_PER_SHUFFLING_PHASE + if shuffle_epoch + WHISK_PROPOSER_SELECTION_GAP + 1 >= WHISK_EPOCHS_PER_SHUFFLING_PHASE: permutation_commitment = WHISK_TRIVIAL_PERMUTATION_COMMITMENT # Require the trivial permutation during cooldown else: permutation_commitment = state.validators[get_beacon_proposer_index(state)].permutation_commitment @@ -225,20 +225,20 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: #### Deposits ```python -def get_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSFrScalar: +def get_unique_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSG1Scalar: counter = 0 while True: - k = BLSFrScalar(hash(uint_to_bytes(validator_index) + uint_to_bytes(counter))) # hash `validator_index || counter` - if is_k_commitment_unique(state, bls.ScalarMultiplication(k, BLS_G1_GENERATOR)): + k = BLSG1Scalar(hash(uint_to_bytes(validator_index) + uint_to_bytes(counter))) # hash `validator_index || counter` + if is_k_commitment_unique(state, bls.BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)): return k # unique by trial and error counter += 1 def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: validator = bellatrix.get_validator_from_deposit(state, deposit) - k = get_whisk_k(state, len(state.validators)) - validator.whisk_tracker = WhiskTracker(BLS_G1_GENERATOR, bls.ScalarMultiplication(k, BLS_G1_GENERATOR)) - validator.whisk_k_commitment = bls.ScalarMultiplication(k, BLS_G1_GENERATOR) + k = get_unique_whisk_k(state, len(state.validators)) + validator.whisk_tracker = WhiskTracker(BLS_G1_GENERATOR, bls.BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)) + validator.whisk_k_commitment = bls.BLSG1ScalarMultiply(k, BLS_G1_GENERATOR) validator.whisk_permutation_commitment = WHISK_TRIVIAL_PERMUTATION_COMMITMENT return validator ``` From 6caafa5db98598db5607090760240ea69f50eba9 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 2 Feb 2022 17:28:45 +0000 Subject: [PATCH 09/16] Update beacon-chain.md --- specs/whisk/beacon-chain.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index 4bb46de73b..242de9b5d2 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -21,7 +21,7 @@ | Name | SSZ equivalent | Description | | - | - | - | -| `BLSG1Scalar` | `Bytes48` | BLS12-381 G1 scalar | +| `BLSScalar` | `Bytes48` | BLS12-381 scalar | | `BLSG1Point` | `Bytes48` | BLS12-381 G1 point | *Note*: A subgroup check MUST be performed when deserializing a `BLSG1Point` for use in any of the functions below. @@ -30,7 +30,7 @@ def BLSG1PointFromAffine(x: int, y: int) -> BLSG1Point -def BLSG1ScalarMultiply(scalar: BLSG1Scalar, point: BLSG1Point) -> BLSG1Point +def BLSG1ScalarMultiply(scalar: BLSScalar, point: BLSG1Point) -> BLSG1Point ``` | Name | Value | @@ -48,8 +48,8 @@ class WhiskShuffleProof: class WhiskTrackerProof: T_1: BLSG1Point # Sigma commitment T_2: BLSG1Point # Sigma commitment - s_1: BLSG1Scalar # Sigma response - s_2: BLSG1Scalar # Sigma response + s_1: BLSScalar # Sigma response + s_2: BLSScalar # Sigma response def IsValidShuffleProof(permutation_commitment: BLSG1Point, pre_shuffle_trackers: Sequence[WhiskTracker], @@ -225,10 +225,10 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: #### Deposits ```python -def get_unique_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSG1Scalar: +def get_unique_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSScalar: counter = 0 while True: - k = BLSG1Scalar(hash(uint_to_bytes(validator_index) + uint_to_bytes(counter))) # hash `validator_index || counter` + k = BLSScalar(hash(uint_to_bytes(validator_index) + uint_to_bytes(counter))) # hash `validator_index || counter` if is_k_commitment_unique(state, bls.BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)): return k # unique by trial and error counter += 1 From 8517acabfc1dbb1a35789e6170b5db0bb2c19c9a Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 26 Feb 2022 22:37:19 +0000 Subject: [PATCH 10/16] Create beacon-chain.md This PR changes the Whisk tracker format to be of the form `(r * pubkey, r * BLS_GT_GENERATOR)` instead of `(r * k * BLS_G1_GENERATOR, r * BLS_G1_GENERATOR)`. This allows for non-interactive tracker registrations from validator pubkeys, removing ~50 lines the code. It also significantly reduces the amount of state overhead. This PR also removes permutation commitments, though those can be easily readded if deemed necessary. --- specs/whisk/beacon-chain.md | 114 ++++++++---------------------------- 1 file changed, 23 insertions(+), 91 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index 242de9b5d2..c0059b7e5a 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -22,7 +22,8 @@ | Name | SSZ equivalent | Description | | - | - | - | | `BLSScalar` | `Bytes48` | BLS12-381 scalar | -| `BLSG1Point` | `Bytes48` | BLS12-381 G1 point | +| `BLSG1Point` | `Bytes48` | compressed BLS12-381 G1 point | +| `BLSGtPoint` | `Vector[Bytes48, 4]` | compressed BLS12-381 Gt point | *Note*: A subgroup check MUST be performed when deserializing a `BLSG1Point` for use in any of the functions below. @@ -33,12 +34,6 @@ def BLSG1PointFromAffine(x: int, y: int) -> BLSG1Point def BLSG1ScalarMultiply(scalar: BLSScalar, point: BLSG1Point) -> BLSG1Point ``` -| Name | Value | -| - | - | -| `BLS_G1_GENERATOR_X` | `0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb` | -| `BLS_G1_GENERATOR_Y` | `0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1` | -| `BLS_G1_GENERATOR` | `BLSG1PointFromAffine(BLS_G1_GENERATOR_X, BLS_G1_GENERATOR_Y)` | - #### Whisk ```python @@ -51,40 +46,26 @@ class WhiskTrackerProof: s_1: BLSScalar # Sigma response s_2: BLSScalar # Sigma response -def IsValidShuffleProof(permutation_commitment: BLSG1Point, - pre_shuffle_trackers: Sequence[WhiskTracker], - post_shuffle_trackers: Sequence[WhiskTracker], - shuffle_proof: WhiskShuffleProof) -> bool: +def IsValidWhiskShuffleProof(pre_shuffle_trackers: Sequence[WhiskTracker], + post_shuffle_trackers: Sequence[WhiskTracker], + shuffle_proof: WhiskShuffleProof) -> bool: """ - Verify `post_shuffle_trackers` is the permutation of `pre_shuffle_trackers` according to `permutation_commitment`. + Verify `post_shuffle_trackers` is a permutation of `pre_shuffle_trackers`. """ -def IsValidTrackerProof(tracker: WhiskTracker, k_commitment: BLSG1Point, tracker_proof: WhiskTrackerProof) -> bool: +def IsValidWhiskOpeningProof(tracker: WhiskTracker, tracker_proof: WhiskTrackerProof) -> bool: """ - Verify knowledge of `k` such that `tracker.k_r_G == k * tracker.r_G` and `k_commitment == k * BLS_G1_GENERATOR`. + Verify knowledge of `privkey` such that `privkey * tracker.Gt_point == e(tracker.G1_point, BLS_G2_GENERATOR)`. """ ``` -| Name | Value | -| - | - | -| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT_X` | `TODO (depends on the shuffle proof CRS)` | -| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT_Y` | `TODO (depends on the shuffle proof CRS)` | -| `WHISK_TRIVIAL_PERMUTATION_COMMITMENT` | `BLSG1PointFromAffine(WHISK_TRIVIAL_PERMUTATION_COMMITMENT_X, WHISK_TRIVIAL_PERMUTATION_COMMITMENT_Y)` | - ### Epoch processing ```python class WhiskTracker(Container): - r_G: BLSG1Point # r * G - k_r_G: BLSG1Point # k * r * G - -class Validator(Container): - # ... - # Whisk - whisk_tracker: WhiskTracker # Whisk tracker (r * G, k * r * G) [New in Whisk] - whisk_k_commitment: BLSG1Point # Whisk k commitment k * BLS_G1_GENERATOR [New in Whisk] - whisk_permutation_commitment: BLSG1Point # Whisk permutation commitment [New in Whisk] + G1_point: BLSG1Point # r * pubkey + Gt_point: BLSGtPoint # r * BLS_GT_GENERATOR class BeaconState(Container): # ... @@ -92,6 +73,7 @@ class BeaconState(Container): whisk_candidate_trackers: Vector[WhiskTracker, WHISK_CANDIDATE_TRACKERS_COUNT] # [New in Whisk] whisk_proposer_trackers: Vector[WhiskTracker, WHISK_PROPOSER_TRACKERS_COUNT] # [New in Whisk] + def select_whisk_trackers(state: BeaconState, epoch: Epoch) -> None: # Select proposer trackers from candidate trackers proposer_seed = get_seed(state, epoch - WHISK_PROPOSER_SELECTION_GAP, DOMAIN_WHISK_PROPOSER_SELECTION) @@ -104,7 +86,7 @@ def select_whisk_trackers(state: BeaconState, epoch: Epoch) -> None: for i in range(WHISK_CANDIDATE_TRACKERS_COUNT): seed = hash(get_seed(state, epoch, DOMAIN_WHISK_CANDIDATE_SELECTION) + uint_to_bytes(i)) candidate_index = compute_proposer_index(state, active_validator_indices, seed) # sample by effective balance - state.whisk_candidate_trackers[i] = state.validators[candidate_index].whisk_tracker + state.whisk_candidate_trackers[i] = WhiskTracker(state[index].pubkey, BLS_GT_GENERATOR) def process_whisk_updates(state: BeaconState) -> None: @@ -129,17 +111,14 @@ class BeaconBlock(Container): whisk_opening_proof: WhiskTrackerProof # [New in Whisk] # ... -def process_whisk_opening_proof(state: BeaconState, block: BeaconBlock) -> None: - tracker = state.whisk_proposer_trackers[state.slot % WHISK_PROPOSER_TRACKERS_COUNT] - k_commitment = state.validators[block.proposer_index].whisk_k_commitment - assert whisk.IsValidTrackerProof(tracker, k_commitment, block.whisk_opening_proof) - - def process_block_header(state: BeaconState, block: BeaconBlock) -> None: # ... # [Removed in Whisk] Verify that proposer index is the correct index # [Removed in Whisk] assert block.proposer_index == get_beacon_proposer_index(state) - process_whisk_opening_proof(state, block) # [New in Whisk] + # Verify opening proof # [New in Whisk] + tracker = state.whisk_proposer_trackers[state.slot % WHISK_PROPOSER_TRACKERS_COUNT] # [New in Whisk] + assert whisk.IsValidWhiskOpeningProof(tracker, block.whisk_opening_proof) # [New in Whisk] + process_whisk_opening_proof(state, block) # ... ``` @@ -153,8 +132,6 @@ class BeaconBlockBody(Container): whisk_shuffle_proof: WhiskShuffleProof # [New in Whisk] whisk_registration_proof: WhiskTrackerProof # [New in Whisk] whisk_tracker: WhiskTracker # [New in Whisk] - whisk_k_commitment: BLSG1Point # [New in Whisk] - whisk_permutation_commitment: BLSG1Point # [New in Whisk] def get_feistel_encryption(index: uint64, rounds: uin64, K: uint64) -> uint64: def F(x): @@ -176,73 +153,28 @@ def get_shuffle_indices(state: BeaconState, epoch: Epoch) -> Sequence[uint64]: return [get_feistel_encryption(index, shuffle_round, WHISK_VALIDATORS_PER_SHUFFLE) for index in row_indices] -def whisk_process_shuffled_trackers(state: BeaconState, body: BeaconBlockBody) -> None: - shuffle_epoch = get_current_epoch(state) % WHISK_EPOCHS_PER_SHUFFLING_PHASE - if shuffle_epoch + WHISK_PROPOSER_SELECTION_GAP + 1 >= WHISK_EPOCHS_PER_SHUFFLING_PHASE: - permutation_commitment = WHISK_TRIVIAL_PERMUTATION_COMMITMENT # Require the trivial permutation during cooldown - else: - permutation_commitment = state.validators[get_beacon_proposer_index(state)].permutation_commitment - +def process_whisk(state: BeaconState, body: BeaconBlockBody) -> None: # Check the shuffle proof shuffle_indices = get_shuffle_indices(state, get_current_epoch(state)) pre_shuffle_trackers = [state.whisk_candidate_trackers[i] for i in shuffle_indices] post_shuffle_trackers = body.whisk_post_shuffle_trackers - shuffle_proof = body.whisk_shuffle_proof - assert whisk.IsValidShuffleProof(permutation_commitment, pre_shuffle_trackers, post_shuffle_trackers, shuffle_proof) + assert whisk.IsValidWhiskShuffleProof(pre_shuffle_trackers, post_shuffle_trackers, body.whisk_shuffle_proof) + + # Require unchanged trackers during cooldown + shuffle_epoch = get_current_epoch(state) % WHISK_EPOCHS_PER_SHUFFLING_PHASE + if shuffle_epoch + WHISK_PROPOSER_SELECTION_GAP + 1 >= WHISK_EPOCHS_PER_SHUFFLING_PHASE: + assert pre_shuffle_trackers == post_shuffle_trackers # Shuffle candidate trackers for i, shuffle_index in enumerate(shuffle_indices): state.whisk_candidate_trackers[shuffle_index] = post_shuffle_trackers[i] -def is_k_commitment_unique(state: BeaconState, k_commitment: BLSG1Point) -> bool: - return all([validator.whisk_k_commitment != k_commitment for validator in state.validators]) - - -def process_whisk(state: BeaconState, body: BeaconBlockBody) -> None: - whisk_process_shuffled_trackers(state, body) - - # Overwrite all validator Whisk fields (first Whisk proposal) or just the permutation commitment (next proposals) - 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 - assert is_k_commitment_unique(state, body.whisk_k_commitment) - assert whisk.IsValidTrackerProof(body.whisk_tracker, body.whisk_k_commitment, body.whisk_registration_proof) - proposer.whisk_tracker = body.whisk_tracker - proposer.whisk_k_commitment = body.whisk_k_commitment - else: # next Whisk proposals - assert body.whisk_registration_proof == WhiskTrackerProof() - assert body.whisk_tracker == WhiskTracker() - assert body.whisk_k_commitment == BLSG1Point() - proposer.whisk_permutation_commitment = body.whisk_permutation_commitment - - def process_block(state: BeaconState, block: BeaconBlock) -> None: # ... process_whisk(state, block.body) # [New in Whisk] ``` -#### Deposits - -```python -def get_unique_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSScalar: - counter = 0 - while True: - k = BLSScalar(hash(uint_to_bytes(validator_index) + uint_to_bytes(counter))) # hash `validator_index || counter` - if is_k_commitment_unique(state, bls.BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)): - return k # unique by trial and error - counter += 1 - - -def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: - validator = bellatrix.get_validator_from_deposit(state, deposit) - k = get_unique_whisk_k(state, len(state.validators)) - validator.whisk_tracker = WhiskTracker(BLS_G1_GENERATOR, bls.BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)) - validator.whisk_k_commitment = bls.BLSG1ScalarMultiply(k, BLS_G1_GENERATOR) - validator.whisk_permutation_commitment = WHISK_TRIVIAL_PERMUTATION_COMMITMENT - return validator -``` - #### `get_beacon_proposer_index` ```python From cf82b1728e11c9bab005a0ab948d735a460bc371 Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 1 Mar 2022 09:33:17 +0000 Subject: [PATCH 11/16] A couple of fixes to the no-registration simplification @asn-d6: Readded a consistency check for `IsValidWhiskOpeningProof` (involving `pubkey` instead of `k_commitment`). --- specs/whisk/beacon-chain.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index c0059b7e5a..2e97a3b0ed 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -40,7 +40,7 @@ def BLSG1ScalarMultiply(scalar: BLSScalar, point: BLSG1Point) -> BLSG1Point class WhiskShuffleProof: ### TODO -class WhiskTrackerProof: +class WhiskOpeningProof: T_1: BLSG1Point # Sigma commitment T_2: BLSG1Point # Sigma commitment s_1: BLSScalar # Sigma response @@ -54,9 +54,9 @@ def IsValidWhiskShuffleProof(pre_shuffle_trackers: Sequence[WhiskTracker], """ -def IsValidWhiskOpeningProof(tracker: WhiskTracker, tracker_proof: WhiskTrackerProof) -> bool: +def IsValidWhiskOpeningProof(pubkey: BLSPubkey, tracker: WhiskTracker, opening_proof: WhiskOpeningProof) -> bool: """ - Verify knowledge of `privkey` such that `privkey * tracker.Gt_point == e(tracker.G1_point, BLS_G2_GENERATOR)`. + Verify the `privkey` for `pubkey` satisfies `privkey * tracker.Gt_point == e(tracker.G1_point, BLS_G2_GENERATOR)`. """ ``` @@ -108,17 +108,20 @@ def process_epoch(state: BeaconState) -> None: class BeaconBlock(Container): # ... proposer_index: ValidatorIndex - whisk_opening_proof: WhiskTrackerProof # [New in Whisk] + whisk_opening_proof: WhiskOpeningProof # [New in Whisk] # ... +def process_whisk_opening_proof(state: BeaconState, block: BeaconBlock) -> None: + pubkey = state.validators[block.proposer_index].pubkey + tracker = state.whisk_proposer_trackers[state.slot % WHISK_PROPOSER_TRACKERS_COUNT] + assert whisk.IsValidWhiskOpeningProof(pubkey, tracker, block.whisk_opening_proof) + + def process_block_header(state: BeaconState, block: BeaconBlock) -> None: # ... # [Removed in Whisk] Verify that proposer index is the correct index # [Removed in Whisk] assert block.proposer_index == get_beacon_proposer_index(state) - # Verify opening proof # [New in Whisk] - tracker = state.whisk_proposer_trackers[state.slot % WHISK_PROPOSER_TRACKERS_COUNT] # [New in Whisk] - assert whisk.IsValidWhiskOpeningProof(tracker, block.whisk_opening_proof) # [New in Whisk] - process_whisk_opening_proof(state, block) + process_whisk_opening_proof(state, block) # [New in Whisk] # ... ``` @@ -130,8 +133,6 @@ class BeaconBlockBody(Container): # Whisk whisk_post_shuffle_trackers: Vector[WhiskTracker, WHISK_VALIDATORS_PER_SHUFFLE] # [New in Whisk] whisk_shuffle_proof: WhiskShuffleProof # [New in Whisk] - whisk_registration_proof: WhiskTrackerProof # [New in Whisk] - whisk_tracker: WhiskTracker # [New in Whisk] def get_feistel_encryption(index: uint64, rounds: uin64, K: uint64) -> uint64: def F(x): From 1cdac2b98bb001fa8dfa764feeb8743eca8d2e20 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 2 Mar 2022 10:40:21 +0000 Subject: [PATCH 12/16] remove unused helpers --- specs/whisk/beacon-chain.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index 2e97a3b0ed..49197f5923 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -27,13 +27,6 @@ *Note*: A subgroup check MUST be performed when deserializing a `BLSG1Point` for use in any of the functions below. -```python -def BLSG1PointFromAffine(x: int, y: int) -> BLSG1Point - - -def BLSG1ScalarMultiply(scalar: BLSScalar, point: BLSG1Point) -> BLSG1Point -``` - #### Whisk ```python From a9be55ce2230ffb4b035523743dcf7c611b7de9b Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 2 Mar 2022 12:51:43 +0000 Subject: [PATCH 13/16] use Mary's suggested tracker --- specs/whisk/beacon-chain.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index 49197f5923..53ef3282d2 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -49,7 +49,7 @@ def IsValidWhiskShuffleProof(pre_shuffle_trackers: Sequence[WhiskTracker], def IsValidWhiskOpeningProof(pubkey: BLSPubkey, tracker: WhiskTracker, opening_proof: WhiskOpeningProof) -> bool: """ - Verify the `privkey` for `pubkey` satisfies `privkey * tracker.Gt_point == e(tracker.G1_point, BLS_G2_GENERATOR)`. + Verify the `privkey` for `pubkey` satisfies `tracker.Gt_point == e(privkey * tracker.G1_point, BLS_G2_SAMPLE)`. """ ``` @@ -57,8 +57,8 @@ def IsValidWhiskOpeningProof(pubkey: BLSPubkey, tracker: WhiskTracker, opening_p ```python class WhiskTracker(Container): - G1_point: BLSG1Point # r * pubkey - Gt_point: BLSGtPoint # r * BLS_GT_GENERATOR + G1_point: BLSG1Point # r * BLS_G1_GENERATOR + Gt_point: BLSGtPoint # r * e(privkey * BLS_G1_GENERATOR, BLS_G2_SAMPLE) class BeaconState(Container): # ... From d11245a8bfede151613014cd00c6fff66618c49e Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 4 Mar 2022 10:02:13 +0000 Subject: [PATCH 14/16] Update beacon-chain.md --- specs/whisk/beacon-chain.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index 53ef3282d2..5927978f48 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -27,17 +27,12 @@ *Note*: A subgroup check MUST be performed when deserializing a `BLSG1Point` for use in any of the functions below. -#### Whisk +#### Whisk (TODO) ```python class WhiskShuffleProof: - ### TODO class WhiskOpeningProof: - T_1: BLSG1Point # Sigma commitment - T_2: BLSG1Point # Sigma commitment - s_1: BLSScalar # Sigma response - s_2: BLSScalar # Sigma response def IsValidWhiskShuffleProof(pre_shuffle_trackers: Sequence[WhiskTracker], post_shuffle_trackers: Sequence[WhiskTracker], From 060a8f3f9d7d996a0f4d47c09293b8345dc09148 Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Sat, 27 Aug 2022 12:33:25 -0700 Subject: [PATCH 15/16] Revert G_t element optimization This needs its own ethresearch post, and some additional analysis to see if we can do the shuffle ZKP in the allowed timeframe. This reverts commit 8517acabfc1dbb1a35789e6170b5db0bb2c19c9a. --- specs/whisk/beacon-chain.md | 83 ++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index 5927978f48..fd1687f4dd 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -23,11 +23,23 @@ | - | - | - | | `BLSScalar` | `Bytes48` | BLS12-381 scalar | | `BLSG1Point` | `Bytes48` | compressed BLS12-381 G1 point | -| `BLSGtPoint` | `Vector[Bytes48, 4]` | compressed BLS12-381 Gt point | *Note*: A subgroup check MUST be performed when deserializing a `BLSG1Point` for use in any of the functions below. -#### Whisk (TODO) +```python +def BLSG1PointFromAffine(x: int, y: int) -> BLSG1Point + + +def BLSG1ScalarMultiply(scalar: BLSScalar, point: BLSG1Point) -> BLSG1Point +``` + +| Name | Value | +| - | - | +| `BLS_G1_GENERATOR_X` | `0x17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb` | +| `BLS_G1_GENERATOR_Y` | `0x08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1` | +| `BLS_G1_GENERATOR` | `BLSG1PointFromAffine(BLS_G1_GENERATOR_X, BLS_G1_GENERATOR_Y)` | + +#### Whisk ```python class WhiskShuffleProof: @@ -42,9 +54,9 @@ def IsValidWhiskShuffleProof(pre_shuffle_trackers: Sequence[WhiskTracker], """ -def IsValidWhiskOpeningProof(pubkey: BLSPubkey, tracker: WhiskTracker, opening_proof: WhiskOpeningProof) -> bool: +def IsValidWhiskOpeningProof(tracker: WhiskTracker, k_commitment: BLSG1Point, tracker_proof: WhiskTrackerProof) -> bool: """ - Verify the `privkey` for `pubkey` satisfies `tracker.Gt_point == e(privkey * tracker.G1_point, BLS_G2_SAMPLE)`. + Verify knowledge of `k` such that `tracker.k_r_G == k * tracker.r_G` and `k_commitment == k * BLS_G1_GENERATOR`. """ ``` @@ -52,8 +64,14 @@ def IsValidWhiskOpeningProof(pubkey: BLSPubkey, tracker: WhiskTracker, opening_p ```python class WhiskTracker(Container): - G1_point: BLSG1Point # r * BLS_G1_GENERATOR - Gt_point: BLSGtPoint # r * e(privkey * BLS_G1_GENERATOR, BLS_G2_SAMPLE) + r_G: BLSG1Point # r * G + k_r_G: BLSG1Point # k * r * G + +class Validator(Container): + # ... + # Whisk + whisk_tracker: WhiskTracker # Whisk tracker (r * G, k * r * G) [New in Whisk] + whisk_k_commitment: BLSG1Point # Whisk k commitment k * BLS_G1_GENERATOR [New in Whisk] class BeaconState(Container): # ... @@ -61,7 +79,6 @@ class BeaconState(Container): whisk_candidate_trackers: Vector[WhiskTracker, WHISK_CANDIDATE_TRACKERS_COUNT] # [New in Whisk] whisk_proposer_trackers: Vector[WhiskTracker, WHISK_PROPOSER_TRACKERS_COUNT] # [New in Whisk] - def select_whisk_trackers(state: BeaconState, epoch: Epoch) -> None: # Select proposer trackers from candidate trackers proposer_seed = get_seed(state, epoch - WHISK_PROPOSER_SELECTION_GAP, DOMAIN_WHISK_PROPOSER_SELECTION) @@ -74,7 +91,7 @@ def select_whisk_trackers(state: BeaconState, epoch: Epoch) -> None: for i in range(WHISK_CANDIDATE_TRACKERS_COUNT): seed = hash(get_seed(state, epoch, DOMAIN_WHISK_CANDIDATE_SELECTION) + uint_to_bytes(i)) candidate_index = compute_proposer_index(state, active_validator_indices, seed) # sample by effective balance - state.whisk_candidate_trackers[i] = WhiskTracker(state[index].pubkey, BLS_GT_GENERATOR) + state.whisk_candidate_trackers[i] = state.validators[candidate_index].whisk_tracker def process_whisk_updates(state: BeaconState) -> None: @@ -100,9 +117,9 @@ class BeaconBlock(Container): # ... def process_whisk_opening_proof(state: BeaconState, block: BeaconBlock) -> None: - pubkey = state.validators[block.proposer_index].pubkey tracker = state.whisk_proposer_trackers[state.slot % WHISK_PROPOSER_TRACKERS_COUNT] - assert whisk.IsValidWhiskOpeningProof(pubkey, tracker, block.whisk_opening_proof) + k_commitment = state.validators[block.proposer_index].whisk_k_commitment + assert whisk.IsValidWhiskOpeningProof(tracker, k_commitment, block.whisk_opening_proof) def process_block_header(state: BeaconState, block: BeaconBlock) -> None: @@ -121,6 +138,9 @@ class BeaconBlockBody(Container): # Whisk whisk_post_shuffle_trackers: Vector[WhiskTracker, WHISK_VALIDATORS_PER_SHUFFLE] # [New in Whisk] whisk_shuffle_proof: WhiskShuffleProof # [New in Whisk] + whisk_registration_proof: WhiskTrackerProof # [New in Whisk] + whisk_tracker: WhiskTracker # [New in Whisk] + whisk_k_commitment: BLSG1Point # [New in Whisk] def get_feistel_encryption(index: uint64, rounds: uin64, K: uint64) -> uint64: def F(x): @@ -142,7 +162,7 @@ def get_shuffle_indices(state: BeaconState, epoch: Epoch) -> Sequence[uint64]: return [get_feistel_encryption(index, shuffle_round, WHISK_VALIDATORS_PER_SHUFFLE) for index in row_indices] -def process_whisk(state: BeaconState, body: BeaconBlockBody) -> None: +def whisk_process_shuffled_trackers(state: BeaconState, body: BeaconBlockBody) -> None: # Check the shuffle proof shuffle_indices = get_shuffle_indices(state, get_current_epoch(state)) pre_shuffle_trackers = [state.whisk_candidate_trackers[i] for i in shuffle_indices] @@ -159,11 +179,52 @@ def process_whisk(state: BeaconState, body: BeaconBlockBody) -> None: state.whisk_candidate_trackers[shuffle_index] = post_shuffle_trackers[i] +def is_k_commitment_unique(state: BeaconState, k_commitment: BLSG1Point) -> bool: + return all([validator.whisk_k_commitment != k_commitment for validator in state.validators]) + + +def process_whisk(state: BeaconState, body: BeaconBlockBody) -> None: + whisk_process_shuffled_trackers(state, body) + + # Overwrite all validator Whisk fields (first Whisk proposal) or just the permutation commitment (next proposals) + 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 + assert is_k_commitment_unique(state, body.whisk_k_commitment) + assert whisk.IsValidWhiskOpeningProof(body.whisk_tracker, body.whisk_k_commitment, body.whisk_registration_proof) + proposer.whisk_tracker = body.whisk_tracker + proposer.whisk_k_commitment = body.whisk_k_commitment + else: # next Whisk proposals + assert body.whisk_registration_proof == WhiskTrackerProof() + assert body.whisk_tracker == WhiskTracker() + assert body.whisk_k_commitment == BLSG1Point() + + def process_block(state: BeaconState, block: BeaconBlock) -> None: # ... process_whisk(state, block.body) # [New in Whisk] ``` +#### Deposits + +```python +def get_unique_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSScalar: + counter = 0 + while True: + k = BLSScalar(hash(uint_to_bytes(validator_index) + uint_to_bytes(counter))) # hash `validator_index || counter` + if is_k_commitment_unique(state, bls.BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)): + return k # unique by trial and error + counter += 1 + + +def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: + validator = bellatrix.get_validator_from_deposit(state, deposit) + k = get_unique_whisk_k(state, len(state.validators)) + validator.whisk_tracker = WhiskTracker(BLS_G1_GENERATOR, bls.BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)) + validator.whisk_k_commitment = bls.BLSG1ScalarMultiply(k, BLS_G1_GENERATOR) + return validator +``` + #### `get_beacon_proposer_index` ```python From 314d0b4fb7966e17f44611c12e85e4b9d7693bcf Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Sat, 27 Aug 2022 13:24:00 -0700 Subject: [PATCH 16/16] Implement new shuffling strategy Ditch the Feistel logic and instead have each shuffler pick the row they shuffle using their RANDAO reveal. --- specs/whisk/beacon-chain.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/specs/whisk/beacon-chain.md b/specs/whisk/beacon-chain.md index fd1687f4dd..892f30456b 100644 --- a/specs/whisk/beacon-chain.md +++ b/specs/whisk/beacon-chain.md @@ -6,7 +6,7 @@ | `WHISK_PROPOSER_TRACKERS_COUNT` | `uint64(2**13)` (= 8,192) | number of proposer trackers | | `WHISK_EPOCHS_PER_SHUFFLING_PHASE` | `Epoch(2**8)` (= 256) | epochs per shuffling phase | | `WHISK_VALIDATORS_PER_SHUFFLE` | `uint64(2**7)` (= 128) | number of validators shuffled per shuffle step | -| `WHISK_SHUFFLE_STEPS_PER_ROUND` | `uint64(2**7)` (= 128) | Feistel permutation steps to complete a pass over all rows | +| `WHISK_SHUFFLE_STEPS_PER_ROUND` | `uint64(2**7)` (= 128) | Number of stirs to complete a pass over all rows | | `WHISK_PROPOSER_SELECTION_GAP` | `Epoch(2)` | gap between proposer selection and the block proposal phase | | Name | Value | @@ -142,29 +142,29 @@ class BeaconBlockBody(Container): whisk_tracker: WhiskTracker # [New in Whisk] whisk_k_commitment: BLSG1Point # [New in Whisk] -def get_feistel_encryption(index: uint64, rounds: uin64, K: uint64) -> uint64: - def F(x): - return (x ** 3) % K # F(x) = x^3 (mod K) is a bijective non-linear function - x, y = index // K, index % K # Compute 2D coordinates (x, y) from 1D coordinates - for _ in range(rounds): # Apply Feistel rounds - x, y = y, (F(y) + x) % K - return x * K + y # Convert 2D coordinates (x, y) back to 1D coordinates +def get_squareshuffle_indices(s: uint64, r: uint64, k: uint64) -> Sequence[uint64]: + """ + Get indices of row `s` in round `r` assuming a square matrix of order `k`. + """ + if r % 2 == 0: # rows get shuffled on even rounds + return [i + k * (s % k) for i in range(k)] # indices of row `s % k` + else: # columns get shuffled on odd rounds + return [s + k * (i % k) for i in range(k)] # indices of column `s % k` -def get_shuffle_indices(state: BeaconState, epoch: Epoch) -> Sequence[uint64]: +def get_shuffle_indices(state: BeaconState, randao_reveal: BLSSignature, epoch: Epoch) -> Sequence[uint64]: """ Return the indices that the Feistel permutation shuffles in this slot. """ shuffle_round = state.slot // WHISK_SHUFFLE_STEPS_PER_ROUND - shuffle_step = state.slot % WHISK_SHUFFLE_STEPS_PER_ROUND - row_indices = [i + WHISK_VALIDATORS_PER_SHUFFLE * shuffle_step for i in range(WHISK_VALIDATORS_PER_SHUFFLE)] - return [get_feistel_encryption(index, shuffle_round, WHISK_VALIDATORS_PER_SHUFFLE) for index in row_indices] + shuffled_row = uint256(hash(randao_reveal)) % WHISK_SHUFFLE_STEPS_PER_ROUND + return get_squareshuffle_indices(shuffled_row, shuffle_round, WHISK_VALIDATORS_PER_SHUFFLE) def whisk_process_shuffled_trackers(state: BeaconState, body: BeaconBlockBody) -> None: # Check the shuffle proof - shuffle_indices = get_shuffle_indices(state, get_current_epoch(state)) + shuffle_indices = get_shuffle_indices(state, body.randao_reveal, get_current_epoch(state)) pre_shuffle_trackers = [state.whisk_candidate_trackers[i] for i in shuffle_indices] post_shuffle_trackers = body.whisk_post_shuffle_trackers assert whisk.IsValidWhiskShuffleProof(pre_shuffle_trackers, post_shuffle_trackers, body.whisk_shuffle_proof)