security

package
v1.26.18 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 12, 2026 License: BSD-3-Clause Imports: 11 Imported by: 0

Documentation

Overview

Package security exposes the chain-wide ChainSecurityProfile to operators, dApps, and auditors through the security JSON-RPC namespace at /ext/security and two REST sidecars at /ext/security/profile and /ext/security/block/{n}.

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 ("security"), 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

View Source
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 security JSON-RPC namespace. Profile may be nil — see ErrNoProfile.

The returned handler is suitable for APIServer.AddRoute(handler, "security", "") so it lands at /ext/security on the node's HTTP listener. REST sidecars are mounted at /profile and /block/{n} on the same handler so the full external surface is:

POST /ext/security                  (JSON-RPC, methods above)
GET  /ext/security/profile          (REST sidecar)
GET  /ext/security/block/{n}        (REST sidecar)

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 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 the blockSecurity RPC (POST /ext/security) and the REST endpoint /ext/security/block/{n}. 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

func NewMetrics(metricsInstance metric.Metrics) *Metrics

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

func (m *Metrics) ObserveMempoolClassicalCredential(chain string)

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

func (m *Metrics) SetZchainProofLagEpochs(epochs float64)

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.
	// "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 the securityProfile RPC (POST /ext/security) and the REST sidecar (GET /ext/security/profile).

Stable shape: every field has a fixed JSON tag, no embedded structs. Adding a new field requires bumping the major version of the security 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 security namespace at /ext/security. Methods are read-only; the underlying profile pointer is set once at construction and never mutated.

Exposed methods:

securityProfile  → ProfileReply
blockSecurity    → BlockSecurityReply

On the wire, gorilla/rpc dispatches these as security_securityProfile and security_blockSecurity (namespace_method). Callers using the REST sidecars hit /ext/security/profile and /ext/security/block/{n} directly. One namespace, two transports, one shape.

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 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 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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL