Files
zooqueen f5ab5e2578 quasar: Pulsar threshold ML-DSA as a Quasar certificate profile
Add protocol/quasar/pulsar -- the consensus-side adapter that wires the
luxfi/pulsar threshold ML-DSA primitives into Quasar as a cert profile.
Consensus consumes Pulsar; Pulsar never consumes consensus.

Decomplected ownership (the load-bearing boundary):

  Consensus owns ORCHESTRATION: validator-set membership, committee
  sampling, session binding, canonical nonce selection, QC aggregation,
  signer bitmap, ConsensusCert assembly, retries, fallback, slashing
  inputs.

  Pulsar owns CRYPTO: the two-round shape (Round1 binds the nonce,
  Round2 emits a proof-carrying z partial), z aggregation mod q, the
  public hint recovery (FindHint over w' = A*z - c*t1*2^d), and the
  final FIPS 204 ML-DSA signature.

PulsarRoundSigner implements luxfi/pulsar's RoundSigner interface:

  Round1   selects the canonical NonceCert via pulsar.CanonicalNonceIndex
           over the session and refuses any non-canonical nonce
           (anti-grind: a coordinator cannot pick w1, hence the
           challenge, by choosing among boundary-clear certs after
           seeing the message).
  Round2   emits one signer's z Partial; pulsar.VerifyZPartial enforces
           the public bindings + the registered ZK correctness check.
  Finalize selects the canonical signer subset
           (pulsar.CanonicalSignerSet), aggregates z
           (pulsar.MergeAggregates), and assembles the two-certificate
           ConsensusCert accountability artifact (signer bitmap +
           transcript root).

The final FIPS 204 signature byte production is secret-bearing (needs
the module matrix A, t1, the challenge, and the per-coefficient hint),
all package-private to luxfi/pulsar, so it stays there: Finalize
delegates to a pluggable SignatureCore. The core is fail-closed until
luxfi/pulsar ships a reviewed Boundary-Cleared / Carry-Elimination
signer (pulsar.ProductionBCCSigningReady); until then Finalize returns
a structurally-complete ConsensusCert with an EMPTY Signature and
ErrProfileNotReady, and the orchestrator falls back to Corona. No
secret residual is ever opened on this path.

Session binding (session.go): the 32-byte session id is a
domain-separated cSHAKE256 over chain_id, epoch, height, round,
block_hash, validator_set_root, joint_pk_id, dkg_transcript_root,
committee_id, signer_set_root, nonce_pool_root, protocol_version --
every field fixed-width for canonical (non-grindable) encoding. Validate
refuses a session with an all-zero security-relevant root (cross-round
replay guard).

NonceCert generation is a BACKGROUND validator subprotocol filling a
NonceCertPool (pool.go) -- never inline on the block hot path. An empty
pool yields ErrNonceCertPoolEmpty (fall back to Corona), never an inline
nonce.

The profile registers by the package convention (like wave_signer.go's
RoundSigner): a self-contained pulsar.RoundSigner whose ConsensusCert
Signature bytes feed the existing QuasarCert.Pulsar slot and verify via
the polaris.go verifyPulsarLeg / pulsarwire.VerifyBytes path already
wired. No change to polaris.go (cert assembly is a separate concern).

go.mod: replace github.com/luxfi/pulsar => ../luxfi/pulsar. The
RoundSigner / NonceCert / CertProfile API lands on the
fix/threshold-mldsa-hint-leak branch and is not in any published tag
(cached v1.1.1 predates it). Remove the replace once a tag carrying the
API ships.

Tests (protocol/quasar/pulsar, all green): contract implementation,
Round1->Round2->Finalize fail-closed fallback, stub-core success wiring,
empty-pool fallback, non-canonical-nonce rejection, full session-id
field binding, session validation. go build ./... green; existing
protocol/quasar suite unchanged and green.
2026-06-21 02:00:25 -07:00

41 lines
2.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package pulsar wires the luxfi/pulsar threshold ML-DSA primitives into
// Quasar as a certificate profile. It is the consensus-SIDE adapter: Quasar
// consumes Pulsar, Pulsar never consumes Quasar.
//
// Decomplected ownership (the load-bearing boundary of this package):
//
// Consensus owns ORCHESTRATION:
// - validator-set membership and committee sampling (prism.Cut)
// - session binding to the consensus round (PulsarSession)
// - canonical, non-grindable nonce selection from the pool
// - QC aggregation of z-shares, signer bitmap, ConsensusCert assembly
// - retries, coarse aborts, fallback policy, slashing inputs
//
// Pulsar owns CRYPTO:
// - the two-round shape (Round1 binds the nonce; Round2 emits a
// proof-carrying z partial)
// - z aggregation modulo q, the public hint recovery (FindHint over the
// public w' = A·z − c·t1·2^d), and the final FIPS 204 ML-DSA signature
//
// The crypto-side contract is luxfi/pulsar's RoundSigner interface. The final
// FIPS 204 signature byte production is secret-bearing (it needs the module
// matrix A, t1, the challenge, and the per-coefficient hint), so it lives
// ONLY inside package pulsar — never here. This package therefore drives the
// orchestration it owns end to end, and delegates the signature-assembly step
// to a pluggable pulsarlib.RoundSigner CORE. The core is fail-closed until the
// lib ships a sound Boundary-Cleared / Carry-Elimination (BCC/CEF) signer with
// externally-reviewed ZK proofs (pulsar.ProductionBCCSigningReady). Until then
// Finalize returns a structurally-complete ConsensusCert with an EMPTY
// Signature and reports ErrProfileNotReady, and the Quasar orchestrator falls
// back to the Corona (Ring-LWE) profile. No secret residual is ever opened on
// this path.
//
// NonceCerts are produced by a BACKGROUND validator NonceTranscript
// subprotocol that fills a NonceCertPool — never inline on the block hot path.
// An empty pool yields ErrNonceCertPoolEmpty (a fallback signal), never an
// inline nonce.
package pulsar