Documentation
¶
Overview ¶
Package security exposes the chain-wide ChainSecurityProfile to operators, dApps, and auditors through the lux JSON-RPC namespace and a thin HTTP-REST sidecar for block-explorer integration.
The handlers in this package are read-only: every shape is derived from the immutable *consensusconfig.ChainSecurityProfile resolved at node bootstrap (Node.initSecurityProfile, F102). There is no per-request re-resolution and no profile mutation surface.
One service, one namespace ("lux"), one fixed JSON shape. The wire vocabulary uses SCREAMING_SNAKE_CASE canonical names (renderName) so audit pipelines can grep on stable identifiers; the underlying scheme IDs come from consensus/config.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNoProfile = errors.New(
"security: node booted without a chain-wide SecurityProfile pin " +
"(genesis carries no SecurityProfile{} block); RPC unavailable")
ErrNoProfile is returned when an RPC call asks for security data but the node booted without a SecurityProfile pin (legacy networks). The error message names the missing pin so an operator who deploys a strict-PQ wallet against a permissive backend sees the mismatch immediately.
Functions ¶
func NewHandler ¶
func NewHandler(logger log.Logger, profile *consensusconfig.ChainSecurityProfile) (http.Handler, error)
NewHandler constructs an http.Handler for the lux JSON-RPC namespace. Profile may be nil — see ErrNoProfile.
The returned handler is suitable for APIServer.AddRoute(handler, "lux", "") so it lands at /ext/lux on the node's HTTP listener.
Types ¶
type BlockSecurityArgs ¶
type BlockSecurityArgs struct {
// BlockNumber is the explorer-visible block height. Unused by
// the current implementation but reserved so the RPC shape is
// stable when per-block details are added.
BlockNumber avajson.Uint64 `json:"blockNumber"`
// Chain is the chain alias ("P", "X", "C") the block belongs to.
// Reserved for the same reason.
Chain string `json:"chain,omitempty"`
}
BlockSecurityArgs is the JSON-RPC argument struct for lux_blockSecurity. The block-number argument is forward-compatible even though today the reply is chain-wide-constant; explorers wire the parameter so the API can evolve without bumping the namespace.
type BlockSecurityReply ¶
type BlockSecurityReply struct {
// SecurityProfileID is the wire byte of the chain's active
// profile at the time the block was accepted. Constant for a
// chain that has not undergone a profile migration.
SecurityProfileID uint32 `json:"security_profile_id"`
// SecurityProfileName mirrors ProfileReply.ProfileName.
SecurityProfileName string `json:"security_profile_name"`
// PulsarMSignatureValid is true iff the chain's finality scheme
// is a Pulsar-M variant. Blocks accepted under this profile have
// already passed the finality-signature gate at the consensus
// layer; this field is the explorer-facing summary.
PulsarMSignatureValid bool `json:"pulsar_m_signature_valid"`
// ProofBackendID is the wire byte of the chain's canonical
// proof backend (first AllowedProofBackends entry). Pre-rendered
// here for explorer convenience.
ProofBackendID uint32 `json:"proof_backend_id"`
// ProofBackendName is the canonical SCREAMING_SNAKE_CASE name of
// the chain's proof backend.
ProofBackendName string `json:"proof_backend_name"`
// PostQuantumEndToEnd mirrors ProfileReply.PostQuantumEndToEnd.
// Duplicated onto the block-level reply so a single block
// fetch carries the full E2E posture.
PostQuantumEndToEnd bool `json:"post_quantum_end_to_end"`
}
BlockSecurityReply is the JSON body returned by lux_blockSecurity and the REST endpoint /v1/block/{n}/security. It enriches a block lookup with the chain-wide security envelope so explorers can show "this block was finalised under profile X with backend Y" without reimplementing profile lookup.
pulsar_m_signature_valid is true iff the chain-wide FinalitySchemeID is a Pulsar-M scheme — the per-block signature is verified by the consensus layer before the block is accepted, so a block reachable via this API is already known-valid. Explorers that want to double-verify can re-fetch the cert envelope; this field is the summary boolean.
proof_backend_id reports the chain-wide canonical proof backend (first entry of AllowedProofBackends). Per-block backend variance is not part of the chain's posture; if a chain accepts multiple backends, the explorer should fetch the cert envelope for the exact backend used.
type Metrics ¶
type Metrics struct {
// contains filtered or unexported fields
}
Metrics owns the Prometheus gauges and counters that expose the active ChainSecurityProfile to the o11y stack. Constructed once at node bootstrap by NewMetrics; the resulting struct is stamped onto the security service and consumed by mempool / bridge call sites via accessor methods.
All gauges carry the (profile_id, profile_name) label pair so a chain that hot-swaps its profile (forbidden today, but possible in future migrations) keeps a stable time series; consumers can group by the label set to count the active profile.
Counters carry the call-site label (chain="P|X|C" for mempool drops, primitive=... for bridge classical-compat traversals) so a single dashboard query slices by the relevant axis.
func NewMetrics ¶
NewMetrics constructs the security-profile metrics and registers them on the supplied registry. The returned struct exposes setters (SetActiveProfile, ObserveMempoolClassicalCredential, SetZchainProofLagEpochs) so the call sites that own the underlying state don't take a direct dependency on the prometheus library.
metricsInstance MUST be non-nil; pass metric.NewWithRegistry with the security namespace from the node bootstrap.
func (*Metrics) ObserveMempoolClassicalCredential ¶
ObserveMempoolClassicalCredential records that the chain admitted a tx whose credential set carries at least one classical secp256k1 credential under a classical-compat profile. Callers MUST invoke this only AFTER the auth-policy gate admits the tx; a refused tx MUST NOT increment the counter.
chain is the chain alias ("P", "X", "C").
func (*Metrics) SetActiveProfile ¶
func (m *Metrics) SetActiveProfile(profile *consensusconfig.ChainSecurityProfile)
SetActiveProfile stamps the active profile's posture onto every gauge. Call once at node bootstrap after the profile is resolved; re-call only when the chain undergoes a profile migration (which is forbidden today but supported by the API surface).
Passing a nil profile resets every gauge to 0 — the explicit "no profile pinned" posture.
func (*Metrics) SetZchainProofLagEpochs ¶
SetZchainProofLagEpochs sets the current Z-Chain proof-lag gauge. The Z-Chain VM calls this on every accepted block; observers alert on a sustained non-zero value.
type ProfileReply ¶
type ProfileReply struct {
// ProfileID is the wire byte of the active profile (see
// consensus/config.ProfileID). Stable across upgrades.
ProfileID uint32 `json:"profile_id"`
// ProfileName is the canonical SCREAMING_SNAKE_CASE name (e.g.
// "LUX_STRICT_PQ"). Audit tooling matches on this name.
ProfileName string `json:"profile_name"`
// ProfileHash is the 48-byte SHA3-384 commitment to the profile
// encoded as a 0x-prefixed lowercase hex string (96 hex chars).
// Distinct profiles produce distinct hashes; identical profiles
// produce identical hashes regardless of allow-list ordering.
ProfileHash string `json:"profile_hash"`
// PostQuantumEndToEnd reports whether every E2E axis (wallet, tx,
// contract-auth, KEM, recovery) is satisfied by a NIST-PQ
// primitive AND every classical primitive is explicitly
// forbidden. Single boolean for dashboard wiring.
PostQuantumEndToEnd bool `json:"post_quantum_end_to_end"`
// NISTFriendly reports whether the chain pins a NIST-aligned
// canonical Lux profile (StrictPQ or FIPS). A profile whose
// HashSuiteID is SHA3_NIST but whose Forbid* bits are disabled
// (the classical-compat fork case) is NOT NIST-friendly: NIST
// alignment is a full-posture predicate, not a hash-suite axis.
NISTFriendly bool `json:"nist_friendly"`
// LuxCanonical reports whether the profile is one of the three
// audit-signed-off-on Lux canonical profiles (StrictPQ, FIPS,
// Permissive). Downstream forks that pin a different ProfileID
// MUST receive false here so audit attestations refuse them.
LuxCanonical bool `json:"lux_canonical"`
// Scheme axes — each rendered as the SCREAMING_SNAKE canonical
// name of the scheme byte. None / Invalid / unknown bytes are
// rendered as the generic "UNKNOWN_0x<hex>" form so a forked
// binary that pins an unknown byte cannot silently masquerade as
// a canonical scheme.
HashSuite string `json:"hash_suite"`
WalletScheme string `json:"wallet_scheme"`
TxScheme string `json:"tx_scheme"`
ContractAuth string `json:"contract_auth"`
ValidatorScheme string `json:"validator_scheme"`
FinalityScheme string `json:"finality_scheme"`
HighValueScheme string `json:"high_value_scheme"`
ProofPolicy string `json:"proof_policy"`
KeyExchange string `json:"key_exchange"`
HighValueKEM string `json:"high_value_kem"`
RecoveryScheme string `json:"recovery_scheme"`
// Forbid* axes — one boolean per refused-primitive class. Audit
// tooling reads these to verify a deployment refuses what it
// claims to refuse. Strict-PQ profiles set every entry true.
ForbidECDSAWallets bool `json:"forbid_ecdsa_wallets"`
ForbidECDSAContractAuth bool `json:"forbid_ecdsa_contract_auth"`
ForbidBLSContractAuth bool `json:"forbid_bls_contract_auth"`
ForbidClassicalKEM bool `json:"forbid_classical_kem"`
RequireTypedTxAuth bool `json:"require_typed_tx_auth"`
ForbidPairings bool `json:"forbid_pairings"`
ForbidKZG bool `json:"forbid_kzg"`
ForbidTrustedSetup bool `json:"forbid_trusted_setup"`
ForbidClassicalSNARKs bool `json:"forbid_classical_snarks"`
ForbidDevProofs bool `json:"forbid_dev_proofs"`
ForbidFallbacks bool `json:"forbid_fallbacks"`
}
ProfileReply is the JSON body returned by lux_securityProfile.
Stable shape: every field has a fixed JSON tag, no embedded structs. Adding a new field requires bumping the major version of the lux namespace; renaming an existing field is forbidden.
The field order in the struct literal MUST match the field order in the locked profile (consensus/config.profiles.go) so a developer reviewing a diff against the canonical profile sees the same shape.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the JSON-RPC handler set registered under the lux namespace at /ext/lux. Methods are read-only; the underlying profile pointer is set once at construction and never mutated.
Exposed methods:
lux_securityProfile → ProfileReply lux_blockSecurity → BlockSecurityReply
The "block" method returns the chain-wide envelope; per-block auth-scheme details remain in the VM's own block JSON (the VMs do not embed a single canonical block-level scheme byte today, so the summary is the most honest thing we can advertise).
func (*Service) BlockSecurity ¶
func (s *Service) BlockSecurity(_ *http.Request, _ *BlockSecurityArgs, reply *BlockSecurityReply) error
BlockSecurity is the lux_blockSecurity JSON-RPC handler. Returns the chain-wide security envelope for the named block.
Today the reply is constant for a given chain (the chain-wide profile applies to every block since the locked-profile is pinned at genesis). Per-block backend variance is not part of the chain's posture; if a chain accepts multiple backends, explorers fetch the cert envelope directly for the exact backend used.
func (*Service) SecurityProfile ¶
func (s *Service) SecurityProfile(_ *http.Request, _ *struct{}, reply *ProfileReply) error
SecurityProfile is the lux_securityProfile JSON-RPC handler.
Returns ErrNoProfile when the node booted without a pin. A populated reply names every field in ProfileReply; the entire shape is deterministic for a given (ProfileID, ProfileHash) pair, so a dApp can cache by hash and refresh only on hash change.