Files
consensus/protocol/quasar/pulsar_sampled_subject.go
zeekayandHanzo Dev 54208c77f4 lint: gofmt the seven files CI flags, and drop an ineffectual map read
golangci-lint has been failing the `lint` job on:

    engine/chain/attestation.go:89:1               (gofmt)
    engine/chain/cert.go:86:1                      (gofmt)
    pkg/wire/policies.go:777:1                     (gofmt)
    protocol/quasar/compact_evidence.go:96:1       (gofmt)
    protocol/quasar/consensus_cert.go:576:1        (gofmt)
    protocol/quasar/pulsar_sampled_cert.go:50:1    (gofmt)
    protocol/quasar/pulsar_sampled_subject.go:18:1 (gofmt)
    engine/chain/integration.go:864:2              (ineffassign)

The gofmt diffs are entirely struct-field comment alignment — no code moves.

The ineffassign is real. followVerifiedBlock did

    pending, exists := rt.Transitive.pendingBlocks[blockID]

and `pending` is never read: the only other mentions are the reassignment
inside `if !exists` and the store on the next line. Only presence matters, so
the value is no longer bound. Behaviour is identical — an already-tracked
block keeps its entry, a new one gets a fresh PendingBlock.

Scope note: `gofmt -l .` lists 30 files here, but `.golangci.yml` sets
`tests: false`, so the 23 test files are excluded and CI never sees them.
Only the 7 it actually flags are touched.

Verified: gofmt clean on all 8, `go build ./engine/... ./protocol/quasar/...
./pkg/wire/...` exits 0, and golangci-lint no longer reports gofmt or
ineffassign anywhere in those trees.

Does NOT address the separate `test` failure — protocol/quasar times out at
900s, which is its own investigation.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 08:18:52 -07:00

131 lines
6.5 KiB
Go

// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// pulsar_sampled_subject.go — the canonical finality SUBJECT M that EVERY
// sampled Mithril committee signs.
//
// # One M, signed by all r-of-m committees
//
// The Pulsar sampled certificate is the ML-DSA analogue of Avalanche's
// repeated-small-sample path: m unpredictably-sampled dealerless Mithril
// committees each emit ONE ordinary FIPS-204 ML-DSA group signature over the
// SAME subject M, and r of them must agree. For that "agreement" to be
// meaningful every committee must sign the byte-identical M — this file builds
// that one M, and is the single authority for its layout.
//
// # What M binds (and why each field)
//
// M = H( "PULSAR_SAMPLED_SUBJECT_V1"
// ‖ chainID ‖ height ‖ round ‖ blockID ‖ stateRoot // the finalized position
// ‖ beamQCHash // the Beam QC over THIS block
// ‖ signerSetID ‖ pChainHeight // the P-Chain-pinned signer set
// ‖ policyID // the finality posture
// ‖ committeePlanHash ) // the frozen sampling plan
//
// - chainID/height/round/blockID/stateRoot pin the exact consensus position,
// so a committee signature for one position can never be replayed for
// another (cross-position non-transferability).
// - beamQCHash binds the Beam quorum certificate that finalized THIS block:
// the sampled cert attests "I am post-quantum evidence for the block Beam
// already certified via this exact QC." It is a commitment to the Beam QC,
// NOT signed by Beam (that would be circular) — Beam signs the position;
// the Pulsar sampled cert signs the position-plus-Beam-QC. A sampled cert is
// therefore non-transferable across different Beam QCs for the same block.
// - signerSetID/pChainHeight pin the validator set the committees were sampled
// from, at the P-Chain height it was pinned at.
// - policyID is the finality posture (FAST / HYBRID_PQ / PQ_ROOT); a signature
// under one posture's M cannot satisfy another posture.
// - committeePlanHash is the single commitment to the ENTIRE sampling plan
// (n, t, m, r, selectionAlgorithmID, sortitionSeed, committeeKeyEraRoot — see
// pulsar_sortition.go). Binding it into M is what makes the committees
// UN-RESELECTABLE: a different plan hashes differently, yields a different M,
// and the committee signatures over the old M will not verify against it. An
// adversary cannot wait to see the block and then re-sample committees it
// controls — the plan is frozen into the very thing the committees sign.
//
// # Distinct subject domain — non-transferability vs the envelope Pulsar leg
//
// The canonical single-committee Pulsar threshold leg (quasar_finality.go,
// QuasarFinalityMessage) signs the envelope finality message under the
// "Lux/ConsensusCert/v1" customization. The sampled subject deliberately uses a
// DISTINCT domain tag AND a DISTINCT cSHAKE customization
// ("Lux/PulsarSampledSubject/v1"), so the two messages can never collide: a
// single-committee threshold signature is NEVER a valid sampled-committee
// signature and vice versa. They are different evidence types attesting the same
// finalized block by different mechanisms, and the subject domain separates them.
// "QUASAR_FINALITY_V1" remains the canonical NAME of the finalized-position value
// both bind; the sampled subject binds that position plus the sampled-specific
// commitments under its own domain.
//
// # Decomplected
//
// This file owns ONLY the subject construction. It decides nothing about which
// committees are sampled (pulsar_sortition.go), whether a cert verifies
// (pulsar_sampled_verify.go), the security budget (pulsar_sampled_security.go),
// or the finality posture (pulsar_sampled_policy.go).
package quasar
// Domain-separation constants for the sampled-cert subject. The domain tag is
// the first absorbed TupleHash part; the customization is the cSHAKE256
// customization string. Both differ from every other quasar subject, so no
// signature over this subject can be reinterpreted under another context.
const (
pulsarSampledSubjectDomain = "PULSAR_SAMPLED_SUBJECT_V1"
pulsarSampledSubjectCustom = "Lux/PulsarSampledSubject/v1"
)
// PulsarSampledSubjectParams are the explicit finality values that, together
// with the frozen committeePlanHash, define the one subject M every sampled
// committee signs. It is a pure value — the single argument to PulsarSampledSubject.
type PulsarSampledSubjectParams struct {
// ChainID/Height/Round/BlockID/StateRoot pin the finalized consensus position.
ChainID uint32
Height uint64
Round uint32
BlockID [32]byte
StateRoot [32]byte
// BeamQCHash commits the Beam quorum certificate that finalized BlockID. It
// binds the sampled cert to the exact Beam QC, making it non-transferable
// across different QCs for the same block. The producer sets it to the hash
// of the Beam QC it is attesting; the verifier recomputes it from the Beam QC
// it independently checked and rejects a mismatch.
BeamQCHash [32]byte
// SignerSetID is the P-Chain-pinned committed validator-set identity the
// committees were sampled from; PChainHeight is the height it was pinned at.
SignerSetID [48]byte
PChainHeight uint64
// PolicyID is the finality posture (EvidencePolicyID), also bound into the
// sortition seed, so the plan and the subject agree on the posture.
PolicyID uint32
// CommitteePlanHash is the committeePlanHash from DeriveCommitteePlan — the
// single commitment to the whole sampling plan. Binding it here freezes the
// committees into the subject the committees sign.
CommitteePlanHash []byte
}
// PulsarSampledSubject builds the one canonical sampled-cert subject M. It is a
// pure, deterministic function of its arguments: every verifier that recomputes
// it from the same (re-derived) inputs obtains the byte-identical M. TupleHash256
// length-prefixes every part, so flipping any single field's bytes — including a
// single bit of committeePlanHash or beamQCHash — changes M.
func PulsarSampledSubject(p PulsarSampledSubjectParams) []byte {
parts := [][]byte{
[]byte(pulsarSampledSubjectDomain),
u32be(p.ChainID),
u64be(p.Height),
u32be(p.Round),
p.BlockID[:],
p.StateRoot[:],
p.BeamQCHash[:],
p.SignerSetID[:],
u64be(p.PChainHeight),
u32be(p.PolicyID),
p.CommitteePlanHash,
}
return tupleHash256RoundDigest(parts, 32, pulsarSampledSubjectCustom)
}