PQ mode is binary. The profile registry is collapsed to three
canonical bundles:
0x01 ProfileStrictPQ ("STRICT") — production PQ chains
0x02 ProfilePermissive ("PERMISSIVE") — testnet / devnet
0x03 ProfileFIPS ("FIPS") — FIPS-aligned superset
Removed pseudo-profiles: HybridProfile, ClassicalProfile, BLSProfile,
ZooStrictPQProfile, HanzoStrictPQProfile, QuasarStrictPQProfile,
AnnulusStrictPQProfile. Chains that need classical primitives pin
ForkClassicalCompatUnsafeProfile (0x80) at the byte layer and own
the audit consequence.
(*ChainSecurityProfile).IsPQ() is the single canonical predicate.
Profile string surface tracks the byte registry exactly: "strict",
"permissive", "fips".
Cross-brand scrub: dropped "Hanzo mesh" doc-comments in pq_mode.go
+ pq_mode_test.go + witness_floor_test.go in favor of "Lux mesh".
Dropped Lux/Zoo/Hanzo enumeration in account_id.go. account_id_test.go
uses fork byte (0x80) instead of zoo/hanzo profile IDs. qblock.go
references LP-0079 instead of cross-org HIP path.
Kill ProfileZooStrictPQ (0x04) and ProfileHanzoStrictPQ (0x05). They
were byte-identical to StrictPQ except for ProfileID + ProfileName —
which makes them not a different profile, just a different label on
the same crypto. Chain identity comes from chain genesis (chain ID,
network ID, NodeID set), not from a sibling profile byte.
One strict-PQ, one way:
ProfileStrictPQ (0x01)
ProfilePermissive (0x02)
ProfileFIPS (0x03)
Removed: ZooStrictPQProfile var, HanzoStrictPQProfile var, ZooStrictPQ()
constructor, HanzoStrictPQ() constructor, strictPQProfileTemplate()
helper, ProfileNameZooStrictPQ const, ProfileNameHanzoStrictPQ const,
ProfileByID switch arms, IsPQ() switch arms, validatePolicy class-
membership arms, witness.go BLS-aggregate-forbid arms, contract_profile.go
classical-refusal arms, parity_with_auth_test cases.
Test inputs that previously used 43114/43113 (Avalanche chain IDs leaked
into test data) now use 96369/96368 (Lux mainnet/testnet) — no cross-
brand contamination in canonical Lux repos.
White-label chains (Hanzo at ~/work/hanzo/node, Zoo at ~/work/zoo/node)
each have their own chain genesis and select ProfileStrictPQ for their
PQ mode; they don't need a sibling profile byte.
First-principles cleanup: family names (Quasar, Annulus) and the Lux
brand prefix belong in papers/docs, not Go identifiers. Canonical names:
ProfileLuxStrictPQ -> ProfileStrictPQ
ProfileLuxPermissive -> ProfilePermissive
ProfileLuxFIPS -> ProfileFIPS
QuasarStrictPQ() -> removed (was 0x06 byte-identical to 0x01)
AnnulusStrictPQ() -> removed (was alias for the above)
ProfileNameLuxStrictPQ etc. -> removed
ProfileZooStrictPQ (0x04) and ProfileHanzoStrictPQ (0x05) kept — chain-
specific siblings, not crypto family branding. Wire-format cSHAKE256
customization tags (LUX_PQ_PERMIT_V1 etc.) intentionally stay brand-
prefixed as domain separators.
IsPQ(), ProfileByID(), validatePolicy() switches updated.
ProfileName strings drop LUX_ prefix: "LUX_STRICT_PQ" -> "STRICT_PQ".
Golden ProfileHash in security_profile_e2e_pq_test.go re-pinned.
Before: protocol/auth/scheme_ids.go declared its own KeyExchangeID type
with values 0x52/0x53 — disjoint from config/pq_mode.go (0x01/0x02) and
node/network/kem/scheme.go (0x62/0x63). Three packages, three numberings
of the same primitive.
After: auth.KeyExchangeID is a type alias of config.KeyExchangeID. The
canonical wire bytes (0x01 = ML-KEM-768, 0x02 = ML-KEM-1024, 0x90 =
X25519Unsafe) live in config and are re-exported by auth. Methods
(String, IsPostQuantum, IsForbiddenInPQMode) are inherited from config.
Dropped: KeyExchangeMLKEM512 (NIST Cat 1, below strict-PQ floor; never
deployed) and KeyExchangeX25519Legacy (renamed to X25519Unsafe to match
config). No production code referenced these.
New test (auth_test package): TestKEMSchemeIDs_AllUseCanonicalNumbering
proves auth's exported KeyExchange* constants are byte-identical to
config's, with wire bytes 0x01 / 0x02 / 0x90.
Before: protocol/auth/account_id.go::DeriveAccountID used customization
"LUX_ACCOUNT_V1" with inputs (chainID, scheme, pubkey). Registry's
DeriveAccountID used "LUX_ACCOUNT_ID_V1" with (profileID, chainID,
scheme, compactPubkey). Same wallet → different 48-byte AccountIDs
across the two paths; the Z-Chain registry would reject every tx
originated by a properly-registered wallet via ErrTxAuthAccountIDMismatch.
After: auth's signature is (profileID, chainID, scheme, compactPubkey)
under "LUX_ACCOUNT_ID_V1" — byte-for-byte identical to registry's
output for identical inputs. The new ProfileID prefix closes the
cross-profile replay class (same wallet on LuxStrictPQ vs LuxFIPS
yields distinct AccountIDs).
Wire change: forward-only. The previous "LUX_ACCOUNT_V1" path was never
deployed to a live chain. VerifyTxAuthEnvelope rebinds via env.ProfileID
which the envelope already carries.
New tests:
protocol/zchain/registry (external test pkg):
TestDeriveAccountID_MatchesRegistryDerivation — auth & registry
produce identical 48-byte outputs across four (profile, chain,
scheme, pubkey) tuples (Lux/Zoo/Hanzo strict-PQ + Lux-permissive).
TestDeriveAccountID_CustomizationTagsMatch — pins
registry.AccountIDCustomization = "LUX_ACCOUNT_ID_V1".
protocol/auth (account_id_test.go rewrites):
TestDeriveAccountID_DistinctProfilesSameKey — five profiles
produce five distinct AccountIDs for the same wallet.
Existing chain/scheme/key-separation tests rewritten under the
new four-arg signature.