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