LPsLux Proposals
Consensus Systems
LP-117

Consensus Finality Signed Message and Quorum Certificate

Draft
Created
2026-08-21

Abstract

This LP specifies the exact bytes a validator signs to attest finality on a Lux chain, and the byte layout of the quorum certificate that aggregates those signatures. It is the authoritative, implementation-independent description of the LUX/chain/vote/v2 signed message and the QuorumCert wire format. An independent node built to this document produces byte-identical signed messages and interoperable certificates.

Motivation

The signed finality message is the object a validator's key commits to when it votes a block final. Forging it is forging finality. It was defined only in the comments and vectors of one implementation, so any second implementation would have had to reverse-engineer it, and a Final LP (LP-182) described a different, signature-incompatible certificate that the shipped code never produced. This LP records the format that actually ships, taken from the code, so the spec and the signed bytes cannot disagree.

The certificate bytes are a frozen external contract: bridges, the DEX settlement receipt, and light clients consume them (LP-305 §7.6). A change to either layout below is a coordinated protocol change across every implementation, never an edit on one side.

Specification

The signed message: LUX/chain/vote/v2

A vote signs, and a verifier recomputes, exactly these bytes, in order, big-endian:

fieldwidthnotes
domain tag18the ASCII LUX/chain/vote/v2 followed by one NUL byte
version2QuorumCertVersion
qc_type1QCFinality
chain_id32
height8
round4
canonical_block_id32the inner execution commitment — the PRIMARY signed object
parent_canonical_id32the inner parent commitment
execution_state_root32post-execution state root
payload_root32transaction/payload root
validator_set_root32epoch / weighted-set commitment; all-zero means unbound
accept10x01 accept, 0x00 reject

Two rules carry the safety of the scheme:

  • The signature commits to the inner canonical identity, never the outer envelope. The proposervm wrapper — proposer, timestamp — is excluded, so two honest wrappers of one execution produce byte-identical vote messages and interoperate. A block with no inner/outer split (a bare in-process VM block, or a fixed-set chain) binds its own id in the canonical_block_id slot; this degrade is resolved in exactly one place so every producer signs the same bytes for the same block.

  • validator_set_root is bound before the accept byte, so a vote is committed to the weighted validator set it was cast under. A cert gathered under set-root R cannot be re-verified as certifying under a different set R′.

The domain tag carries its own version. A v2 (canonical-commitment) signature can never be confused with a signature over an outer id.

The certificate: QuorumCert

The certificate is stored and transported with this layout, big-endian. It carries both the signed canonical identity and the outer transport ids; the outer ids are a lookup/gossip key and are not part of the signed message above.

fieldwidthsigned?
version2
type1
tier1— (Nova or Quasar, the accept/export rung this cert attests)
chain_id32yes
height8yes
round4yes
block_id32no — outer envelope, transport cache key
parent_id32no — outer envelope parent
canonical_block_id32yes
parent_canonical_id32yes
execution_state_root32yes
payload_root32yes
validator_set_root32yes
threshold4
vote_count4
then vote_count records
  node_id20
  accept1
  sig_len4
  sigsig_len

A decoder MUST reject a frame it could not itself produce:

  • the accept byte in each vote record is 0x00 or 0x01; any other value is corrupt (framing malleability — two byte strings must not name one cert);
  • vote_count × (20 + 1 + 4) must not exceed the remaining buffer, and each sig_len must not exceed what remains, before any allocation (a peer-supplied count must never size an allocation);
  • no trailing bytes remain after the last vote record.

Tier semantics

tier records the rung the cert attests, not a weaker verification path. Both tiers verify against the live committee's threshold:

  • Quasar is export finality: two-thirds of stake at the epoch.
  • Nova is local-execution finality at the committee majority, deliberately sub-Byzantine. It is sound because nothing exportable reads a Nova-only height; the accept threshold for either tier is the live committee's, never a value the cert declares about itself.

Rationale

The layout is a hand-written fixed-width encoding rather than a schema with optional fields, so a field cannot silently drop out of the signature: adding one is a visible change to this table and to the domain-tag version. Signing the inner canonical identity rather than the outer envelope is what let a single fix resolve a class of finality-halt and interoperability defects — two wrappers of one execution are one decision.

Backwards Compatibility

This LP documents the shipped v2 format; it introduces no change. It supersedes the certificate description in LP-182, which specifies a ZAP-schema cert the code does not produce. Any node built to LP-182's certificate would be signature-incompatible with the network. LP-182 should be reduced to the parts that ship, or retired.

Reference Implementation

github.com/luxfi/consensus, engine/chain/cert.go (canonicalVoteMessageFor, the signed message) and engine/chain/cert_codec.go (MarshalQuorumCert / UnmarshalQuorumCert, the certificate wire). The byte vectors in engine/chain/cert_wire_vector_test.go are hand-derived from this layout and pin it against regression.

Security Considerations

The signed message is the finality authority. Every field the finality decision depends on is inside it, and the outer envelope — which an attacker can vary without changing the decision — is deliberately outside it. A conformance suite across implementations MUST assert byte-equality of the signed message for a shared corpus of positions; a divergence there is a fork or a forgery surface, not a compatibility nuisance.