genesis: pin ChainSecurityProfile into Config + Resolve at load (closes F102 genesis layer)

Adds SecurityProfile to genesis.Config and ConfigOutput as an optional
pin-by-ID + pin-by-hash for the chain-wide ChainSecurityProfile. JSON
shape:

  "securityProfile": {
    "profileID": 1,
    "profileHashHex": "<96 hex chars of SHA3-384 ComputeHash>"
  }

SecurityProfile.Resolve() loads the canonical profile via
consensus/config.ProfileByID, runs Validate(), recomputes ComputeHash,
and refuses on any mismatch — a forked binary that swaps in a different
canonical profile content fails genesis boot.

This closes the genesis half of red-team F102: previously this package
never imported consensus/config.ChainSecurityProfile so no genesis
file could enforce the locked profile. The node-side wiring lands in
the follow-up commit.

Bumps luxfi/consensus v1.22.63 → v1.23.5 (the version that ships
ProfileByID, ProfileHash binding, and the closures for F96/F100/F101/
F107/F109).

Adds 6 regression tests for the load + JSON round-trip path.
This commit is contained in:
Hanzo AI
2026-05-10 20:56:31 -07:00
parent 4f23ef1402
commit 8edc83efc0
6 changed files with 313 additions and 4 deletions
+4 -4
View File
@@ -10,7 +10,7 @@ require (
github.com/luxfi/go-bip39 v1.1.2
github.com/luxfi/ids v1.2.9
github.com/luxfi/log v1.4.1
github.com/luxfi/math v1.2.3
github.com/luxfi/math v1.4.0
github.com/luxfi/node v1.23.1
github.com/luxfi/tls v1.0.3
github.com/luxfi/utxo v0.2.4
@@ -26,7 +26,7 @@ require (
github.com/luxfi/atomic v1.0.0 // indirect
github.com/luxfi/compress v0.0.5 // indirect
github.com/luxfi/concurrent v0.0.3 // indirect
github.com/luxfi/database v1.17.40 // indirect
github.com/luxfi/database v1.18.3 // indirect
github.com/luxfi/keychain v1.0.2 // indirect
github.com/luxfi/ordering v0.0.1 // indirect
github.com/luxfi/runtime v1.0.1 // indirect
@@ -47,11 +47,11 @@ require (
github.com/luxfi/address v1.0.1
github.com/luxfi/cache v1.2.1 // indirect
github.com/luxfi/codec v1.1.4 // indirect
github.com/luxfi/consensus v1.22.63 // indirect
github.com/luxfi/consensus v1.23.5 // indirect
github.com/luxfi/formatting v1.0.1
github.com/luxfi/geth v1.16.73 // indirect
github.com/luxfi/math/big v0.1.0 // indirect
github.com/luxfi/metric v1.4.12 // indirect
github.com/luxfi/metric v1.5.0 // indirect
github.com/luxfi/mock v0.1.1 // indirect
github.com/luxfi/p2p v1.18.9 // indirect
github.com/luxfi/protocol v0.0.3
+10
View File
@@ -91,6 +91,8 @@ github.com/luxfi/concurrent v0.0.3 h1:eJyv1fhaC0jMLMw6+QS774cUmp7GK+ouMgvLCqnC7c
github.com/luxfi/concurrent v0.0.3/go.mod h1:Aj/FR5NpM0cB2P4Nt3+tz9+dV6V+LUW4HuMgSjwq5hw=
github.com/luxfi/consensus v1.22.63 h1:1oRAOvSjfeC5IwDkWHgFTHflQFwhCNYItkfh19UmzZk=
github.com/luxfi/consensus v1.22.63/go.mod h1:PwMubdgQloJiYQPawTTJGhsaC4Ve3/BjCmNxTDjYdGg=
github.com/luxfi/consensus v1.23.5 h1:bfEukqgDLOM1/NouHcVES2nxlbwrIKKZYgXyBXNJVCQ=
github.com/luxfi/consensus v1.23.5/go.mod h1:YZfPqGx7NXlnd1x+RdKgV+TrtRPhUz4kdSQqpZoVDh8=
github.com/luxfi/constants v1.4.6 h1:9a/ED2t/sRfeOfvNur2lssyjaw2I7tSyddTJHVSDYWg=
github.com/luxfi/constants v1.4.6/go.mod h1:hOszZ2NDQ8gMZKncfcZ67PXkb5OIbnwAzXC3oFbQwW0=
github.com/luxfi/container v0.0.4 h1:BXhF82WyfqVP5mjlNcr7tP0Fcnvl0Ap1rkiu+rq5XuM=
@@ -99,6 +101,8 @@ github.com/luxfi/crypto v1.18.4 h1:n8/Udhv0RUW7j1ooEKflWnCNWDaunAsWrDXBHvHLLo4=
github.com/luxfi/crypto v1.18.4/go.mod h1:h4LZbaHs3EX6t5sh+psT1JFrLRloTP8PfWSvGNFYtT8=
github.com/luxfi/database v1.17.40 h1:RPtRLbKu5yBM5fCSjR7CWzNqR0N1fg8r9y73hUww5sM=
github.com/luxfi/database v1.17.40/go.mod h1:du1q0PemBc0gZcdtHhsARs6mIsZM3a5R/C040DVYLCM=
github.com/luxfi/database v1.18.3 h1:gg+xwhKUxXa7fDoOD8IS91E71QqoEtcDCl2nfS61Jgg=
github.com/luxfi/database v1.18.3/go.mod h1:sv0pYCGKlK1aNJTICxFUDpVWCJTigoLlshHmV/1pg7c=
github.com/luxfi/formatting v1.0.1 h1:ZnE1rAdEUds9yAegdVdGDOBGN6hLMPOv6E03Fp8IEYo=
github.com/luxfi/formatting v1.0.1/go.mod h1:mYzNf5DJOiqSSKUPzNj5dKy4tstFbN3pZlkI5716eKc=
github.com/luxfi/geth v1.16.73 h1:jbRaWQDXTadZ+fEh5Cd5jnkv1qq4yAoT5bVQFuql8ZU=
@@ -115,10 +119,14 @@ github.com/luxfi/log v1.4.1 h1:rIfFRodb9jrD/w7KayaUk0Oc+37PaQQdKEEMJCjR8gw=
github.com/luxfi/log v1.4.1/go.mod h1:64IE3xRMJcpkQwnPUfJw3pDj7wU0kRS7BZ9wM7R72jk=
github.com/luxfi/math v1.2.3 h1:BgvIFw/srPXFLbcqtoDhLJOfmBsn86GPA1iWgsoyUb4=
github.com/luxfi/math v1.2.3/go.mod h1:C8STnF2H+D6rqBPt248CiWY2TGuJgdtv/+4UqrT15iM=
github.com/luxfi/math v1.4.0 h1:/sb7Grw3hfO+5INWAWdB95jTvCeXg8fSQxsxDzcFtd4=
github.com/luxfi/math v1.4.0/go.mod h1:iW0FOCC8qF2mPE+MakG780CAHA83848lb1L04thA1Pg=
github.com/luxfi/math/big v0.1.0 h1:Vz4c0RsZVPdIKPsHPgAJChH/R3p15WHRUz7LkLf+NIQ=
github.com/luxfi/math/big v0.1.0/go.mod h1:BuxSu22RbO93xBLk5Eam5nldFponoJ73xDFz4uJ3Huk=
github.com/luxfi/metric v1.4.12 h1:rdT1zdG3qsWjTTZprfPZoqZxXx8ZYWNwq2UYe0c70Y8=
github.com/luxfi/metric v1.4.12/go.mod h1:EAFmkzd5EX654G48HXKIsyJufOUa4sxmOJKvwSRbjng=
github.com/luxfi/metric v1.5.0 h1:11PKOjH6ntnnPF5GpspXFAqoKG9raqxT+zW5mN2jztI=
github.com/luxfi/metric v1.5.0/go.mod h1:PkD4D4JoGuyKtfUkqPNYkrg9xKrJeNVZFdW/5XvAe/A=
github.com/luxfi/mock v0.1.1 h1:0HEtIjg1J6CWz+IUyP6rsGqNWTcmxjFnSQIhaDuARwY=
github.com/luxfi/mock v0.1.1/go.mod h1:jo35akl3Vtd8LbzDts8VJ0jmSVycrd1/eBi6g6t5hKU=
github.com/luxfi/node v1.23.1 h1:bfpd9udWuDo6+TPB6Un6d96kM0yr7QU15Wwa2N1oQZI=
@@ -179,6 +187,7 @@ github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTU
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
@@ -201,6 +210,7 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+1
View File
@@ -107,6 +107,7 @@ func GetConfigFile(filepath string) (*Config, error) {
ZChainGenesis: output.ZChainGenesis,
GChainGenesis: output.GChainGenesis,
KChainGenesis: output.KChainGenesis,
SecurityProfile: output.SecurityProfile,
Message: output.Message,
}, nil
}
+121
View File
@@ -0,0 +1,121 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package genesis
import (
"encoding/hex"
"errors"
"fmt"
consensusconfig "github.com/luxfi/consensus/config"
)
// security_profile.go — genesis-level binding of the chain-wide
// ChainSecurityProfile. Closes red-team finding F102: previously
// neither this package nor luxfi/node imported the locked profile,
// so the profile was pure documentation that no consumer checked.
//
// Wire form is pin-by-ID + pin-by-hash:
// - ProfileID names the canonical ChainSecurityProfile (e.g. 0x01 =
// LuxStrictPQ).
// - ProfileHashHex is the 48-byte SHA3-384 ComputeHash of the
// canonical profile, hex-encoded. The genesis loader recomputes
// the hash at boot from consensusconfig.ProfileByID(ProfileID) and
// refuses to start if the hex does not match. Any drift in the
// canonical profile content invalidates every prior genesis that
// pinned its hash.
//
// One-way load: deserialise → resolve → validate → ComputeHash →
// constant-time compare. There is no upgrade path that re-derives the
// hash from the pinned ID alone; that would defeat the lock.
// SecurityProfile is the genesis-level pin for a chain's locked
// ChainSecurityProfile. ProfileID identifies the canonical row in the
// consensusconfig registry; ProfileHashHex pins the expected content
// hash. Both fields are mandatory on a profile-bound genesis.
type SecurityProfile struct {
// ProfileID is the wire byte that names the canonical profile.
// 0x01 = LuxStrictPQ, 0x02 = LuxPermissive, 0x03 = LuxFIPS.
// 0x80+ is reserved for downstream / white-label profiles (which
// must register with the consensus team to obtain a byte).
ProfileID uint8 `json:"profileID"`
// ProfileHashHex is the SHA3-384 ComputeHash of the canonical
// ChainSecurityProfile at the time genesis was sealed, hex-encoded
// (96 hex chars, no 0x prefix). The genesis loader rejects a
// startup whose live ComputeHash does not match.
ProfileHashHex string `json:"profileHashHex"`
}
// SecurityProfileLoadErr is returned by Resolve when the pin fails to
// resolve to a known canonical profile or its content hash mismatches.
var (
ErrSecurityProfileMissing = errors.New("genesis: security profile pin is absent on a profile-required genesis")
ErrSecurityProfileInvalidID = errors.New("genesis: SecurityProfile.ProfileID is not a known consensus profile")
ErrSecurityProfileInvalidHashHex = errors.New("genesis: SecurityProfile.ProfileHashHex is not 48 bytes of hex")
ErrSecurityProfileValidateFailed = errors.New("genesis: canonical profile failed Validate() at boot")
ErrSecurityProfileHashMismatch = errors.New("genesis: live ComputeHash() does not match genesis-pinned ProfileHashHex")
ErrSecurityProfileComputeHashFail = errors.New("genesis: canonical profile ComputeHash() returned error")
)
// Resolve loads the canonical ChainSecurityProfile named by ProfileID,
// validates it, computes its hash, and confirms the hash matches the
// genesis pin. Returns the canonical profile on success.
//
// This is the single load-and-verify entry point. Every consumer of
// the locked profile (node bootstrap, signer registration, peer-cert
// gate, mempool, validator registry, bridge oracle) calls Resolve once
// at startup and threads the returned *ChainSecurityProfile through
// the rest of the process — no re-resolution per request.
//
// The constant-time hash comparison is intentional: a runtime mutation
// of the canonical profile that produces a different hash MUST fail
// the genesis pin, and we don't want to leak which byte of the hash
// matched via an early-exit byte loop. (Hash comparisons are not
// secrets, but consistency with the rest of the security gate is
// worth more than the four-line shortcut.)
func (s *SecurityProfile) Resolve() (*consensusconfig.ChainSecurityProfile, error) {
if s == nil {
return nil, ErrSecurityProfileMissing
}
pinned, err := hex.DecodeString(s.ProfileHashHex)
if err != nil || len(pinned) != 48 {
return nil, fmt.Errorf("%w: have %d bytes (%v)", ErrSecurityProfileInvalidHashHex, len(pinned), err)
}
profile, err := consensusconfig.ProfileByID(consensusconfig.ProfileID(s.ProfileID))
if err != nil {
return nil, fmt.Errorf("%w: 0x%02x: %v", ErrSecurityProfileInvalidID, s.ProfileID, err)
}
if err := profile.Validate(); err != nil {
return nil, fmt.Errorf("%w: %v", ErrSecurityProfileValidateFailed, err)
}
live, err := profile.ComputeHash()
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrSecurityProfileComputeHashFail, err)
}
if !constantTimeEqual48(live[:], pinned) {
return nil, fmt.Errorf("%w: pinned=%s live=%x",
ErrSecurityProfileHashMismatch, s.ProfileHashHex, live[:])
}
// Stamp the live hash so consumers always see it on the struct.
profile.ProfileHash = live
return profile, nil
}
// constantTimeEqual48 returns true iff the two 48-byte hashes are equal,
// in constant time over the byte slice contents. Subtle.ConstantTimeCompare
// is generic over byte slices but pinned to 48 bytes here so a length
// mismatch is caught explicitly above.
func constantTimeEqual48(a, b []byte) bool {
if len(a) != 48 || len(b) != 48 {
return false
}
var v byte
for i := 0; i < 48; i++ {
v |= a[i] ^ b[i]
}
return v == 0
}
+164
View File
@@ -0,0 +1,164 @@
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package genesis
import (
"encoding/hex"
"encoding/json"
"errors"
"strings"
"testing"
consensusconfig "github.com/luxfi/consensus/config"
)
// TestSecurityProfile_Resolve_LuxStrictPQ proves a genesis pin for the
// canonical LuxStrictPQ profile resolves, validates, and round-trips
// through ComputeHash without mutation. Closes F102 at the genesis
// load layer: the profile that ships in consensus/config is reachable
// from the genesis package and its content hash is checked.
func TestSecurityProfile_Resolve_LuxStrictPQ(t *testing.T) {
canonical := consensusconfig.LuxStrictPQ()
live, err := canonical.ComputeHash()
if err != nil {
t.Fatalf("LuxStrictPQ().ComputeHash() returned error: %v", err)
}
pin := &SecurityProfile{
ProfileID: uint8(consensusconfig.ProfileLuxStrictPQ),
ProfileHashHex: hex.EncodeToString(live[:]),
}
got, err := pin.Resolve()
if err != nil {
t.Fatalf("Resolve() returned error: %v", err)
}
if got == nil {
t.Fatalf("Resolve() returned nil profile")
}
if got.ProfileID != uint32(consensusconfig.ProfileLuxStrictPQ) {
t.Errorf("Resolve() returned profile with ProfileID=%d; want %d",
got.ProfileID, consensusconfig.ProfileLuxStrictPQ)
}
if got.ProfileHash != live {
t.Errorf("Resolve() did not stamp the live hash onto the returned profile")
}
}
// TestSecurityProfile_Resolve_HashMismatchRejected proves the pin
// refuses a profile whose live hash diverges from the pinned hex.
// This is the load-bearing F102 anti-regression: a forked binary that
// swaps in a different canonical profile content must fail genesis
// boot.
func TestSecurityProfile_Resolve_HashMismatchRejected(t *testing.T) {
// Pin a wrong hash for LuxStrictPQ.
pin := &SecurityProfile{
ProfileID: uint8(consensusconfig.ProfileLuxStrictPQ),
ProfileHashHex: strings.Repeat("00", 48), // 96 hex zeros
}
_, err := pin.Resolve()
if err == nil {
t.Fatal("Resolve() accepted a wrong-hash pin")
}
if !errors.Is(err, ErrSecurityProfileHashMismatch) {
t.Errorf("Resolve() returned %v; want ErrSecurityProfileHashMismatch", err)
}
}
// TestSecurityProfile_Resolve_UnknownProfileIDRejected proves the
// pin refuses an unknown ProfileID byte.
func TestSecurityProfile_Resolve_UnknownProfileIDRejected(t *testing.T) {
pin := &SecurityProfile{
ProfileID: 0xFE, // unregistered
ProfileHashHex: strings.Repeat("ab", 48),
}
_, err := pin.Resolve()
if err == nil {
t.Fatal("Resolve() accepted an unknown ProfileID")
}
if !errors.Is(err, ErrSecurityProfileInvalidID) {
t.Errorf("Resolve() returned %v; want ErrSecurityProfileInvalidID", err)
}
}
// TestSecurityProfile_Resolve_InvalidHashHexRejected proves
// non-hex / wrong-length hash strings are refused.
func TestSecurityProfile_Resolve_InvalidHashHexRejected(t *testing.T) {
cases := map[string]string{
"empty": "",
"short": "abcd",
"odd": strings.Repeat("a", 95),
"too long": strings.Repeat("ab", 49),
"not hex": strings.Repeat("zz", 48),
"wrong padding": "0x" + strings.Repeat("ab", 48), // hex.DecodeString refuses 0x prefix
}
for name, hashHex := range cases {
pin := &SecurityProfile{
ProfileID: uint8(consensusconfig.ProfileLuxStrictPQ),
ProfileHashHex: hashHex,
}
_, err := pin.Resolve()
if err == nil {
t.Errorf("%s: Resolve() accepted an invalid hash hex %q", name, hashHex)
continue
}
if !errors.Is(err, ErrSecurityProfileInvalidHashHex) {
t.Errorf("%s: Resolve() returned %v; want ErrSecurityProfileInvalidHashHex", name, err)
}
}
}
// TestSecurityProfile_Resolve_NilRejected proves a missing pin is
// detected at the API boundary.
func TestSecurityProfile_Resolve_NilRejected(t *testing.T) {
var pin *SecurityProfile
_, err := pin.Resolve()
if !errors.Is(err, ErrSecurityProfileMissing) {
t.Errorf("nil pin: Resolve() returned %v; want ErrSecurityProfileMissing", err)
}
}
// TestConfig_SecurityProfile_JSONRoundtrip proves the SecurityProfile
// field survives a Config → ConfigOutput → JSON → ConfigOutput round
// trip. Closes the wire-form half of F102.
func TestConfig_SecurityProfile_JSONRoundtrip(t *testing.T) {
canonical := consensusconfig.LuxStrictPQ()
live, err := canonical.ComputeHash()
if err != nil {
t.Fatalf("ComputeHash: %v", err)
}
pin := &SecurityProfile{
ProfileID: uint8(consensusconfig.ProfileLuxStrictPQ),
ProfileHashHex: hex.EncodeToString(live[:]),
}
out := &ConfigOutput{
NetworkID: 12345,
Message: "test",
SecurityProfile: pin,
}
raw, err := json.Marshal(out)
if err != nil {
t.Fatalf("json.Marshal: %v", err)
}
if !strings.Contains(string(raw), `"securityProfile"`) {
t.Fatalf("marshalled output missing securityProfile field: %s", raw)
}
var got ConfigOutput
if err := json.Unmarshal(raw, &got); err != nil {
t.Fatalf("json.Unmarshal: %v", err)
}
if got.SecurityProfile == nil {
t.Fatal("SecurityProfile was nil after round-trip")
}
if got.SecurityProfile.ProfileID != pin.ProfileID {
t.Errorf("ProfileID = %d; want %d", got.SecurityProfile.ProfileID, pin.ProfileID)
}
if got.SecurityProfile.ProfileHashHex != pin.ProfileHashHex {
t.Errorf("ProfileHashHex = %q; want %q", got.SecurityProfile.ProfileHashHex, pin.ProfileHashHex)
}
if _, err := got.SecurityProfile.Resolve(); err != nil {
t.Errorf("Resolve() after round-trip returned %v", err)
}
}
+13
View File
@@ -42,6 +42,17 @@ type Config struct {
// This allows chains (P/X/C/Q/etc.) to have network-specific IDs that
// can be migrated/upgraded over time via governance.
ChainMapping *ChainMapping `json:"chainMapping,omitempty"`
// SecurityProfile is the genesis-level pin for this chain's locked
// ChainSecurityProfile (consensus/config). Carries the ProfileID
// byte plus the SHA3-384 content hash of the canonical profile.
// luxfi/node loads this at startup and refuses to boot if the live
// canonical profile content doesn't match the pinned hash.
//
// Optional in JSON so legacy genesis files keep parsing; node
// bootstrap warns + refuses strict-PQ features when absent.
// Closes red-team finding F102.
SecurityProfile *SecurityProfile `json:"securityProfile,omitempty"`
}
// Allocation represents a genesis allocation
@@ -265,6 +276,7 @@ type ConfigOutput struct {
GChainGenesis string `json:"gChainGenesis,omitempty"` // G-Chain: Graph VM genesis
KChainGenesis string `json:"kChainGenesis,omitempty"` // K-Chain: KMS VM genesis
Chains []ChainEntry `json:"chains,omitempty"` // Additional genesis chains
SecurityProfile *SecurityProfile `json:"securityProfile,omitempty"`
Message string `json:"message"`
}
@@ -320,6 +332,7 @@ func (c *Config) ToJSON(hrp string) *ConfigOutput {
ZChainGenesis: c.ZChainGenesis,
GChainGenesis: c.GChainGenesis,
KChainGenesis: c.KChainGenesis,
SecurityProfile: c.SecurityProfile,
Message: c.Message,
}
}