Skip to content

feat(types): carry proposer signature outside the block proof#467

Draft
MegaRedHand wants to merge 1 commit into
mainfrom
feat/proposer-signature-outside-block-proof
Draft

feat(types): carry proposer signature outside the block proof#467
MegaRedHand wants to merge 1 commit into
mainfrom
feat/proposer-signature-outside-block-proof

Conversation

@MegaRedHand

@MegaRedHand MegaRedHand commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

What

Moves the proposer signature outside the block proof. SignedBlock.proof becomes a two-field BlockProof:

before:  block-proof = aggregate([prop-sig, att0, att1])
after:   block-proof = (prop-sig, aggregate([att0, att1]))

before (no atts):  aggregate([prop-sig])
after  (no atts):  (prop-sig, empty-proof)
struct SignedBlock { message: Block, proof: BlockProof }

struct BlockProof {
    proposer_signature: XmssSignature,         // raw XMSS sig (reuses the existing fixed-size wire type)
    attestation_proof:  MultiMessageAggregate, // Type-2 over body attestations only (empty if none)
}

The proposer signature reuses the existing XmssSignature type (SszVector<u8, SIGNATURE_SIZE>) already carried by SignedAttestation.signature — no new wrapper type. sign_block_root already returns an XmssSignature, so propose_block carries it verbatim; genesis anchors use the existing blank_xmss_signature() placeholder.

Why

Previously the proposer signature was wrapped as a singleton Type-1 and merged into a single block Type-2 alongside every attestation, so even a block with zero attestations required a prover call, and the merge could only run once the block root was known.

Decoupling the proposer:

  • lets the attestation aggregate be built independently of the block root — a prerequisite for proposer prebuild;
  • removes all prover work from the empty-attestation case;
  • the proposer signature is now verified directly with the hash-based XMSS verifier (ValidatorSignature::is_valid), never entering the lean-multisig prover/verifier.

Changes

Area Change
types/block.rs New BlockProof { proposer_signature: XmssSignature, attestation_proof }; SignedBlock.proof: BlockProof. Default = blank XMSS placeholder + empty aggregate
blockchain/lib.rs propose_block: carry the raw XmssSignature directly; aggregate attestation Type-1s only (skip the merge entirely when there are no attestations)
blockchain/store.rs verify_block_signatures: verify raw proposer sig vs proposal_pubkey, then verify the attestation Type-2 over attestation components only; reject a stray aggregate on an attestation-less block (UnexpectedAttestationProof)
blockchain/reaggregate.rs Drop the proposer component from the split layout; split from attestation_proof
storage/store.rs Persist / reconstruct BlockProof in the BlockSignatures row
net/p2p, net/rpc Construction-site updates

Test status

  • cargo check --workspace --all-targets: ✅
  • cargo clippy --workspace --all-targets -- -D warnings: ✅
  • Library unit tests (types/storage/blockchain/rpc/p2p --lib): ✅ (incl. SignedBlock SSZ round-trip with a populated proposer signature + blank-default)
  • Spec tests fail by design: this diverges from the leanSpec #799 single-merged-proof wire format, so signature_spectests / ssz_spectests fail against the current cross-client fixtures until those are regenerated. The fixture crate carries only a minimal type-only compile fix (no adapter logic).

Open questions

  • Wire/cross-client: needs a matching leanSpec change + regenerated fixtures before this can interop.

🤖 Draft — opened for review of the restructure.

Split `SignedBlock.proof` into `BlockProof { proposer_signature,
attestation_proof }`. The proposer's raw XMSS signature is now carried as
a standalone field and verified directly with the hash-based XMSS verifier,
while `attestation_proof` is a lean-multisig Type-2 over the block body's
attestations only.

Previously the proposer signature was wrapped as a singleton Type-1 and
merged into a single block Type-2 alongside every attestation, so even a
block with zero attestations needed a prover call. Decoupling the proposer
lets the attestation aggregate be built independently of the block root (a
prerequisite for proposer prebuild) and removes prover work from the
empty-attestation case.

  before:  block-proof = aggregate([prop-sig, att0, att1])
  after:   block-proof = (prop-sig, aggregate([att0, att1]))

  before (no atts): aggregate([prop-sig])
  after  (no atts): (prop-sig, empty-proof)

Verification now checks the raw proposer signature against the proposer's
proposal pubkey, then verifies the attestation Type-2 over attestation
components only (and rejects a stray aggregate on an attestation-less block).
Reaggregation drops the proposer component from the split layout.

NOTE: this diverges from the leanSpec #799 single-merged-proof wire format,
so the signature/SSZ spec tests fail against the current cross-client
fixtures until those are regenerated for the new layout. Draft PR.
@MegaRedHand MegaRedHand force-pushed the feat/proposer-signature-outside-block-proof branch from 8494da8 to eff6b8b Compare June 24, 2026 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant