Skip to content

feat(consensus): BLS endorsement sender + wire format (IIP-52)#4842

Draft
envestcc wants to merge 2 commits into
iotexproject:masterfrom
envestcc:bls-aggregate-commit
Draft

feat(consensus): BLS endorsement sender + wire format (IIP-52)#4842
envestcc wants to merge 2 commits into
iotexproject:masterfrom
envestcc:bls-aggregate-commit

Conversation

@envestcc

Copy link
Copy Markdown
Member

Depends on #4841 — stacked on top of the BLS key plumbing PR. Once #4841 merges, this PR's diff will collapse to just the new changes.

Sender + wire-format half of the BLS signature aggregation work tracked in IIP-52. Receiver verification + endorsement-manager quorum integration land in a follow-up PR; with the feature gate parked at `IsToBeEnabled` this PR is dead code in production.

What's in (new vs. #4841)

  • `endorsement.BLSEndorsement` — new Go type holding (timestamp, endorser iotex address, BLS signature). Endorser is the delegate's iotex address, not the BLS pubkey; receivers look up the registered pubkey from candidate state, mirroring the BlockFooter aggregate verification model.
  • `EndorsedConsensusMessage` — polymorphic over `*Endorsement` and `*BLSEndorsement`, round-trips through `ConsensusMessage.endorsement` and `ConsensusMessage.bls_endorsement`. At most one is set per message.
  • `ProofOfLock` — internal struct that carries either `[]*Endorsement` or `[]*BLSEndorsement` so the proof-of-lock cascade doesn't sprout if/else branches at every callsite. `blockProposal`, `roundCtx.proofOfLock`, and the `BlockProposal` proto Serialize / LoadProto all flow through it.
  • `ConsensusConfig.BLSAggregationEnabled(height)` — feature gate wired off `Genesis.ToBeEnabledBlockHeight`. Will be re-pointed at a named hardfork height once the full Phase-2 stack lands.
  • Sender side:
    • `rollDPoSCtx.newEndorsement` now signs PROPOSAL, LOCK and COMMIT votes with BLS post-fork (skipping delegates without a configured BLS key).
    • `rollDPoSCtx.endorseBlockProposal` signs the proposer's wrapping endorsement with BLS post-fork. Block header signing remains on the ECDSA producer key — that signature ties the block to a chain identity and is unrelated to the consensus vote layer.
    • The proposer's `producerKey` (ECDSA + BLS + address) is threaded through `Proposal / mintNewBlock / endorseBlockProposal` so the branch can pick the right key without a separate lookup.
  • iotex-proto bump to `envestcc/iotex-proto@03a99cc` (PR #169): `BlockProposal.bls_endorsements` added so post-fork proof-of-lock can ride the wire.

What's deferred to the follow-up PR

  • Endorsement manager BLS collection + DB persistence
  • `roundCtx.AddBLSVoteEndorsement`
  • `RollDPoS.HandleConsensusMsg` BLS dispatch + signature verification (`CheckBlockProposer` / `CheckVoteEndorser` BLS path)
  • BLS pubkey lookup mechanism (from candidate state at round start)
  • Proof-of-lock BLS replay in `CheckBlockProposer` (a TODO marker is in place where BLS endorsements are decoded but not yet counted toward majority)
  • End-to-end tests for the BLS COMMIT / relock path

Why all three topics, not just COMMIT

The original IIP-52 motivation (compress the on-chain footer) only requires aggregating COMMIT votes. Implementation-wise though, keeping PROPOSAL/LOCK on ECDSA while COMMIT runs on BLS creates a mixed-mode coupling at the lock check: a delegate that emitted COMMIT (BLS) but not PROPOSAL still counts toward locking, but the proof-of-lock would then need to carry mixed-type entries on the wire. Switching all three topics to BLS post-fork keeps the lock check / proof-of-lock paths uniform and leaves room for future LOCK aggregation in BlockProposal relay (`bls_endorsements` field would then aggregate). The CPU cost (extra ~36 ms verify per 5-second round) is negligible.

Test plan

  • `go build ./...`
  • `go vet ./...`
  • Targeted tests pass: `TestNewRollDPoSCtx`, `TestCheckVoteEndorser`, `TestCheckBlockProposer`, etc. (13 tests)
  • Full BLS COMMIT path tests land with the receiver PR

🤖 Generated with Claude Code

…te (IIP-52)

Scaffolding for the BLS signature aggregation work tracked in IIP-52. No
behavior change yet: EnableBLSAggregation is gated on IsToBeEnabled, and the
BLS keys plumbed into rollDPoSCtx are not yet used to sign or verify
endorsements.

- blockchain/config.go: add Chain.BLSProducerPrivKey (comma-separated hex)
  and BLSProducerPrivateKeys(). Empty value falls back to deriving each BLS
  key from the corresponding ECDSA producer key via
  crypto.GenerateBLS12381PrivateKey.
- consensus/scheme/rolldpos: Builder.SetBLSPriKey; NewRollDPoSCtx accepts
  []*crypto.BLS12381PrivateKey aligned 1:1 with producer ECDSA keys;
  rollDPoSCtx stores them on blsPriKeys for the upcoming signing path.
- consensus/consensus.go: wire SetBLSPriKey(cfg.Chain.BLSProducerPrivateKeys()).
- action/protocol/context.go: FeatureCtx.EnableBLSAggregation gated on
  g.IsToBeEnabled(height); flips to a named hardfork height later.
- go.mod: bump iotex-proto to envestcc/iotex-proto bls-aggregate (52e72a6)
  for the BlockFooter aggregated_signature / signer_bitmap fields and the
  BLSEndorsement message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@envestcc envestcc requested a review from a team as a code owner May 27, 2026 04:00
@envestcc envestcc marked this pull request as draft May 28, 2026 02:25

@envestcc envestcc May 28, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is no need to add a new BLSEndorsement; we can simply continue using Endorsement. The length of the signature should be sufficient to distinguish whether it is a BLS or an ECDSA signature. This way, many parts of the code would not need to be modified.

@envestcc envestcc force-pushed the bls-aggregate-commit branch from 9080cc1 to 73e2d57 Compare May 28, 2026 04:16
@envestcc

Copy link
Copy Markdown
Member Author

Refactored per the review suggestion — force-pushed 73e2d57.

Endorsement is now reused as-is for BLS post-fork; signature length (65B secp256k1 vs 96B BLS) is the discriminator. The endorser field keeps its existing semantics (secp256k1 producer pubkey), so Endorser().Address() still resolves the iotex address; receivers will look up the registered BLS verifying key from candidate state by that address.

Dropped:

  • endorsement.BLSEndorsement Go type
  • EndorsedConsensusMessage polymorphism (back to single endorsement field)
  • ProofOfLock internal struct (back to []*Endorsement)
  • blockProposal proto serialize/deserialize branching

Also amended iotex-proto PR #169 to drop the standalone BLSEndorsement message and the bls_endorsement / bls_endorsements fields on ConsensusMessage / BlockProposal. The proto changes there are now just the BlockFooter aggregate fields plus a comment clarifying Endorsement.signature semantics. New SHA: e4439ef.

Diff size: 12 files / +396 → 6 files / +147. Build and 15 targeted tests still green.

(I tried to update the PR body to reflect this too, but GitHub's GraphQL keeps 5xx'ing on edits — sorry for the stale description until that clears.)

return msgs, nil
}

// newBLSEndorsements signs the consensus vote with each producer's BLS key

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to separate a new BLSEndorsements method, or can we directly modify the existing newEndorsement method?

Switches consensus vote signing to BLS12-381 post-fork, reusing the
existing Endorsement type and dispatching on signature length (65B
secp256k1 vs 96B BLS). Receiver verification and endorsement-manager
quorum integration land in a follow-up PR; with the feature gate parked
at IsToBeEnabled this commit is dead code in production.

- endorsement.EndorseBLS / VerifyBLSEndorsement: thin helpers that
  produce / verify a regular *Endorsement whose signature field carries
  a BLS sig. Endorser remains the delegate's secp256k1 producer key so
  the existing Endorser().Address() path still resolves the iotex
  address; receivers look up the BLS verifying key from candidate state
  by that address.
- ConsensusConfig.BLSAggregationEnabled(height): feature gate wired off
  Genesis.ToBeEnabledBlockHeight. Will be re-pointed at a named hardfork
  height once the full Phase-2 stack lands.
- rollDPoSCtx.newEndorsement / endorseBlockProposal: post-fork, sign
  PROPOSAL, LOCK and COMMIT votes plus the proposer's wrapping
  endorsement with BLS (skipping delegates without a configured BLS
  key). Block header signing remains on the ECDSA producer key — that
  signature ties the block to chain identity and is unrelated to the
  consensus vote layer.
- The proposer's producerKey (ECDSA + BLS + address) is threaded
  through Proposal / mintNewBlock / endorseBlockProposal so the branch
  can pick the right key without a separate lookup.
- iotex-proto bump to envestcc/iotex-proto@e4439ef (PR iotexproject#169): clarifies
  Endorsement.signature semantics (pre-fork 65B secp256k1, post-fork
  96B BLS, distinguished by length).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@envestcc envestcc force-pushed the bls-aggregate-commit branch from 73e2d57 to ef3235d Compare May 28, 2026 04:44
@envestcc

Copy link
Copy Markdown
Member Author

Done in ef3235d — merged newBLSEndorsements back into newEndorsement with a single signVote helper that branches internally on useBLS. Also tightened endorseBlockProposal to use the same helper, dropping its duplicated ECDSA/BLS branch. The rolldposctx.go portion of the diff drops from +80 to +51 lines.

@sonarqubecloud

Copy link
Copy Markdown

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