nx_crypsis.nx source
↩ module page · 210 lines · 8658 B
1// nx_crypsis.nx -- byte-level camouflage / polymorphism.
2//
3// Biology: crypsis is concealment by resemblance to background or
4// to common harmless objects. Stick insects look like twigs, leaf
5// insects look like leaves, octopus camouflage is real-time pattern
6// matching against substrate. Predators that hunt by sight skip
7// because they don't recognize the prey AS prey.
8//
9// SUBSTRATE EQUIVALENT, per user 2026-05-19 directive: same logical
10// payload, varied byte signature each emission. Vendor's signature-
11// based detection ML sees no two cells alike -- pattern-match
12// confidence stays below threshold so the detection rule never
13// fires. The PAYLOAD is functionally identical; the WIRE FORMAT is
14// different every time.
15//
16// Three V1 mechanisms:
17// 1. **XOR mask rotation** -- a per-emission random mask XOR'd over
18// the payload byte-by-byte. Reversible with the same mask;
19// mask is included in the emission header (variable position).
20// 2. **Padding randomization** -- inject N random padding bytes
21// between functional regions; padding count varies 0-15 per
22// emission. Doesn't change semantics; changes shape.
23// 3. **Allocation-layout variance** -- when cells are emitted, the
24// relative order of independent fields is shuffled across
25// emissions so byte-position-based signatures don't anchor.
26//
27// THE LIMITS: crypsis defeats SIGNATURE detection. It doesn't defeat
28// BEHAVIORAL detection (a scanner observing "this region runs code
29// at address X every N microseconds" sees the behavior regardless
30// of byte signature). The judo is layered: crypsis fools static
31// scanners, aposematism fools heuristic scanners, decoy fools
32// dynamic scanners. Each layer wins SOME percentage; stacked they
33// approach unity.
34//
35// Composes:
36// nx_methyl -- methyl mark wraps the post-crypsis bytes so
37// self-verification still works (the mask + payload
38// are jointly signed; the mark validates the
39// masked-and-unmasked content)
40// nx_decoy -- decoy artifacts get crypsis too so they don't
41// look like static-pattern honeypots
42// nx_pamp -- our own pamp can decoder-then-scan: unmask
43// first, scan the original bytes, so substrate
44// doesn't false-positive its own emissions
45//
46// V1 ships XOR-mask + padding-random. Allocation-layout variance is
47// scheduled for V2 because it requires changing the emit ABI and
48// touches more code than this primitive's scope.
49//
50// Gap list (V1 honest perf verdict):
51// - XOR-mask is reversible-by-mask; a scanner that knows the
52// protocol can extract the mask and unmask in O(n). Crypsis
53// is THROUGHPUT-defense (raises cost-per-scan), not
54// CONFIDENTIALITY (use nx_cipher for that)
55// - mask is caller-supplied randomness (V2 uses substrate-managed
56// CSPRNG sourced from nx_methyl chain)
57// - no allocation-layout variance (V2)
58// - no per-class signature deduplication (an scanner could correlate
59// N cells of the same class via behavior even if bytes differ)
60//
61// genealogy_id: cardinal_2026-05-19_mimicry_obfuscation_directive +
62// biology_crypsis_stick_insect
63// lineage_id: substrate_crypsis_v1
64//
65// nx_safety_envelope:
66// intended_use: "Byte-level polymorphism to defeat static-
67// pattern detection; cooperative with own
68// decoder via mask header"
69// sil_target: SIL1
70// evidence: [reversible_with_mask, no_semantic_change,
71// captain_moroni_aligned_defensive_only]
72// verdict: NOT_YET_EVALUATED
73
74import "nx_syscalls.nx"
75import "nx_tier.nx"
76
77// ===== Sealed enum: NxCrypsisVerdict ==============================
78
79const NX_CRYP_OK: nx_int = 0
80const NX_CRYP_ERR_DST_TOO_SMALL: nx_int = 1
81const NX_CRYP_ERR_BAD_MASK: nx_int = 2
82
83// ===== Struct: NxCrypsisHeader ====================================
84//
85// Wire format of a crypsis-emitted artifact. The receiver reads this
86// header, extracts mask + padding_count, and decodes the payload.
87//
88// mask is the 8-byte XOR mask repeating across the payload bytes.
89// pad_count is how many random padding bytes follow before payload.
90// payload_len is the unmasked-payload length.
91
92struct NxCrypsisHeader {
93 magic: nx_int, // sanity: caller knows the protocol
94 mask: nx_size, // 8-byte XOR mask
95 pad_count: nx_int, // 0-15 padding bytes
96 payload_len: nx_size,
97}
98
99const NX_CRYPSIS_MAGIC: nx_int = 0x4e58435950534953 // "NXCYPSIS"
100
101// ===== nx_crypsis_encode_size ====================================
102//
103// Bytes required in dst for a payload of n bytes with pad_count
104// padding bytes. dst_size = sizeof(header) + pad_count + n.
105
106func nx_crypsis_encode_size(payload_len: nx_size, pad_count: nx_int) -> nx_size {
107 // header: 4 fields * 8 bytes = 32
108 return 32 + (pad_count as nx_size) + payload_len
109}
110
111// ===== nx_crypsis_encode =========================================
112//
113// Encode `payload[0..n]` into `dst` with the supplied mask + padding.
114// dst must be at least nx_crypsis_encode_size(n, pad_count) bytes.
115// pad_count is clamped to 0..15. The encoded form is:
116// [header: magic, mask, pad_count, payload_len]
117// [pad_count random bytes (caller-supplied via pad_bytes[])]
118// [payload XOR mask, repeating mask byte-by-byte]
119
120func nx_crypsis_encode(payload: *u8,
121 payload_len: nx_size,
122 mask: nx_size,
123 pad_bytes: *u8,
124 pad_count: nx_int,
125 dst: *u8,
126 dst_cap: nx_size) -> nx_int {
127 var pad: nx_int = pad_count
128 if pad < 0 { pad = 0 }
129 if pad > 15 { pad = 15 }
130 let need: nx_size = nx_crypsis_encode_size(payload_len, pad)
131 if dst_cap < need { return NX_CRYP_ERR_DST_TOO_SMALL }
132
133 // Write header (32 bytes).
134 let hdr: *NxCrypsisHeader = dst as *NxCrypsisHeader
135 hdr.magic = NX_CRYPSIS_MAGIC
136 hdr.mask = mask
137 hdr.pad_count = pad
138 hdr.payload_len = payload_len
139
140 let body_off: nx_size = 32
141
142 // Copy random padding bytes (caller supplies entropy).
143 var i: nx_size = 0
144 while i < (pad as nx_size) {
145 dst[body_off + i] = pad_bytes[i]
146 i = i + 1
147 }
148
149 // XOR-mask payload byte-by-byte. mask cycles every 8 bytes.
150 var j: nx_size = 0
151 while j < payload_len {
152 let shift: nx_int = ((j as i64) & 7) * 8
153 let mb: nx_int = (mask >> shift) & 255
154 let pb: nx_int = (payload[j] as i64) & 255
155 dst[body_off + (pad as nx_size) + j] = ((pb ^ mb) & 255) as u8
156 j = j + 1
157 }
158 return NX_CRYP_OK
159}
160
161// ===== nx_crypsis_decode =========================================
162//
163// Inverse: read header from src, validate magic, extract mask +
164// payload_len, XOR-mask back to original bytes in dst.
165
166func nx_crypsis_decode(src: *u8,
167 src_len: nx_size,
168 dst: *u8,
169 dst_cap: nx_size,
170 out_payload_len: *i64) -> nx_int {
171 if src_len < 32 { return NX_CRYP_ERR_DST_TOO_SMALL }
172 let hdr: *NxCrypsisHeader = src as *NxCrypsisHeader
173 if hdr.magic != NX_CRYPSIS_MAGIC { return NX_CRYP_ERR_BAD_MASK }
174 let pad: nx_int = hdr.pad_count
175 let payload_len: nx_size = hdr.payload_len
176 if dst_cap < payload_len { return NX_CRYP_ERR_DST_TOO_SMALL }
177 let body_off: nx_size = 32 + (pad as nx_size)
178 if src_len < body_off + payload_len { return NX_CRYP_ERR_DST_TOO_SMALL }
179
180 let mask: nx_size = hdr.mask
181 var j: nx_size = 0
182 while j < payload_len {
183 let shift: nx_int = ((j as i64) & 7) * 8
184 let mb: nx_int = (mask >> shift) & 255
185 let cb: nx_int = (src[body_off + j] as i64) & 255
186 dst[j] = ((cb ^ mb) & 255) as u8
187 j = j + 1
188 }
189 out_payload_len[0] = payload_len as i64
190 return NX_CRYP_OK
191}
192
193// ===== nx_crypsis_signatures_match ===============================
194//
195// Test predicate: given two crypsis-encoded emissions of the SAME
196// logical payload, do their wire-byte signatures DIFFER? Returns 1
197// if signatures differ (crypsis succeeded), 0 if identical (a
198// detector could still anchor). Smoke uses this to verify that
199// different masks produce different signatures.
200
201func nx_crypsis_signatures_match(a: *u8, a_len: nx_size,
202 b: *u8, b_len: nx_size) -> nx_int {
203 if a_len != b_len { return 0 }
204 var i: nx_size = 0
205 while i < a_len {
206 if a[i] != b[i] { return 0 }
207 i = i + 1
208 }
209 return 1
210}