fix: bind checkpoint transition queues to the verified terminal header#423
Conversation
|
I think the immediate transition check is correct, but I’m not sure we should accept future For a checkpoint at epoch Given Could we either reject any |
99267a0 to
872aac1
Compare
|
Good point, I added a comment in |
…425) Builds on #423 (which builds on #422 and #402). Addresses #217. Changes: -Step 3 now accumulates a node-key to BLS-key map and requires each checkpoint account's consensus_public_key to equal the BLS key accumulated for that node from the verified history. Mismatch returns the new CheckpointConsensusKeyMismatch error variant. The reverse membership check uses the same map. -Add test_checkpoint_verifier_binds_consensus_keys_to_terminal_header covering both directions: the honest case (accounts carry the real, accumulated consensus keys and verify) and a swapped consensus key (rejected with CheckpointConsensusKeyMismatch). The shared checkpoint_verification_fixture gains a tamper_consensus_key parameter that swaps one active account's stored consensus key while leaving its node key, status, and the accumulated set intact. -Bind checkpoint consensus keys to the verified terminal header.
Builds on #426 (which builds on #425, #423, #422, and #402). Addresses #311. This only adds test coverage, the issue is not reachable. The decoded ConsensusState, including every validator_accounts entry, is committed by checkpoint.data, which verify_checkpoint_chain binds to the terminal finalized header via checkpoint_hash == sha256(checkpoint.data). Appending a Joining account changes the digest, so the tampered checkpoint no longer matches the honest terminal header and is rejected at Step 2. Changes: -types/src/checkpoint.rs: test_checkpoint_verifier_rejects_extra_joining_account — reproduces the attack directly. It decodes the honest checkpoint, injects an extra Joining account (leaving the active signing set untouched), re-encodes, and asserts verify_checkpoint_chain rejects it with CheckpointHashMismatch. -application/src/actor.rs: rejects_block_with_mismatched_checkpoint_hash — the consensus-layer half. It mirrors accepts_ordinary_child_inside_epoch, changing only the block's checkpoint_hash, and asserts handle_verify returns false, isolating the checkpoint_hash check as the sole cause.
Builds on #422 (which builds on #402)
Addresses #216.
Changes:
verify_checkpoint_chain_with_weak_subjectivitybinding the decoded queues to the terminal header's committed deltas — requirecheckpoint_state.get_added_validators(next_epoch)to equalterminal.added_validators()andcheckpoint_state.get_removed_validators()to equalterminal.removed_validators()(order-normalized), rejecting mismatches with a newCheckpointVerificationError::CheckpointTransitionQueueMismatch.next_epoch(possible when validator warm-up spans more than one epoch) are committed by finalized headers after the terminal and cannot be authenticated by a chain ending at this epoch; they are bound only once the next epoch's boundary header is verified.test_checkpoint_verifier_binds_transition_queues_to_terminal_header(honest empty-queue checkpoint verifies; a checkpoint carrying a pendingremoved_validatorsentry while the terminal header commits an empty set is rejected, with hash/membership/position checks still passing). Parameterizecheckpoint_verification_fixturewith the checkpoint'sremoved_validatorsqueue and update its callers.