refactor(testing): merge the verify-proof fixtures and deduplicate aggregation#850
Merged
tcoratger merged 1 commit intoJun 5, 2026
Conversation
…gregation The two proof-verification fixture files duplicated the alternate-head sentinel, three tamper classes, and the false-positive swap guard. The single-message vector is the degenerate one-component case of the multi-message vector, so the tamper vocabulary is now shared: one set of five tamper classes where component_index defaults to 0, with the single-message fixture accepting the three that apply to it. The identical leaf-aggregation idiom (sign each validator, zip with public keys, aggregate into a single-message proof) appeared four times: twice across the verify fixtures and twice in the key manager. The existing sign-and-aggregate method already implemented it, so it gains an optional precomputed-signature lookup and all four sites collapse onto it. The only remaining raw aggregation call is the recursive two-level fold, which is genuinely unique. Changes: - one verify_proofs module replaces the two fixture files; the sentinel, tamper classes, component-index guard, alternate-head rebind, and participant-key swap guard exist once - tamper renames: the Component-prefixed variants disappear (RebindComponentToAlternateHeadRoot -> RebindToAlternateHeadRoot, IncrementComponentSlot -> IncrementEmittedSlot, SwapComponentParticipantPublicKey -> SwapParticipantPublicKey, SwapComponentMessageBindings -> SwapMessageBindings, DropComponentMessageBinding -> DropMessageBinding); the single spec's bare index field becomes participant_index - the key manager's attestation-proof builder delegates to sign-and-aggregate instead of repeating the aggregation idiom Generated vectors are unchanged: the fixtures emit the same fields and the aggregation inputs are byte-identical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Item 3 of the
packages/testing/audit (follow-up to #848, #849). Two findings:verify_single_message_proofs.pyandverify_multi_message_proofs.pyduplicated theALTERNATE_HEAD_ROOTsentinel, three tamper classes, and the false-positive swap guard — single-message is the degeneratecomponent_index == 0case of multi-message.SingleMessageAggregate.aggregate(children=[], raw_xmss=zip(indices, keys, signatures), ...)) appeared 4 times: in both verify fixtures and twice inkeys.py.Changes
Two fixture files → one (
test_fixtures/verify_proofs.py):component_index: int = 0— single-message tests stay terse (RebindToAlternateHeadRoot()), multi-message tests are explicit (RebindToAlternateHeadRoot(component_index=1)). The single fixture validatescomponent_index == 0and its union excludes the two inherently multi-component tampersRebindComponentToAlternateHeadRoot→RebindToAlternateHeadRoot,IncrementComponentSlot→IncrementEmittedSlot,SwapComponentParticipantPublicKey→SwapParticipantPublicKey,SwapComponentMessageBindings→SwapMessageBindings,DropComponentMessageBinding→DropMessageBinding; the single spec's bareindexfield becomesparticipant_index4× aggregation copy → 1:
XmssKeyManager.sign_and_aggregatealready implemented the idiom, so it gains an optionalprecomputed_signatureslookup and all four sites collapse onto it — both fixture helpers (_aggregate_flat,_single_message_aggregate) are deleted, the child-group pre-aggregation in the recursive path delegates too, andbuild_attestation_proofsbecomes a thin comprehension over it. The only remaining rawaggregate(...)call is the two-level recursive fold, which is genuinely unique.Vector impact: none
The fixtures emit the same client-visible fields and the aggregation inputs are byte-identical (
sign_and_aggregatefetches the same public keys the fixtures fetched themselves). Tamper objects areexclude=Trueand never reach JSON.Verification
just checkpasses (ruff, format, ty, codespell, mdformat)tests/consensus/lstar/verify_proofs: 21/21 pass (all tamper paths, including the renamed multi-message ones)tests/consensus/lstar/state_transition/test_justification.py: 21/21 pass (exercises the rewrittenbuild_attestation_proofs+ precomputed-signature path in block building)🤖 Generated with Claude Code