code wiki / _hdl_build / nx_coe_frame.nx
nx_coe_frame.nx source
↩ module page · 288 lines · 9724 B
1// nx_coe_frame.nx -- CHAIN-OF-EVIDENCE FRAME (F701 keystone + F702 signature, v1)
2// Emits a 5-pillar evidence frame as an ed25519-SIGNED row on an append-only CID hash-chain, then
3// VERIFIES the whole chain deny-by-default: re-hash every row (integrity) + ed25519_verify every
4// signature (authenticity) + check prev-hash linkage (chain). A forged OR tampered row -> RED.
5// Pillars: provenance | custody | environment | content-CID (immutability) | corroboration (re-hash + sig).
6// v1 signs with a dedicated deterministic CoE key (RFC-8032 ed25519, reproducible; pubkey printed).
7// Honest: production key custody (offline private seed / vault) = F702b. Append-only, never deletes.
8// Composes proven libs: nx_str/nx_syscalls/nx_sha256 + nx_ed25519_signature (via nx_fw_capsule lineage).
9// x86-lane. license_tier: ORIGINAL
10import "nx_str.nx"
11import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
12import "nx_syscalls.nx"
13import "nx_sha256.nx"
14import "nx_ed25519_signature.nx"
15const CF_MAGIC_1024: i64 = 1024
16const CF_MAGIC_1500: i64 = 1500
17
18const CF_CAP: i64 = 1048576
19const CF_MAXROWS: i64 = 4096
20
21func cf_puts(s: *u8) -> i64 { sys_write(1, s, nx_str_len(s)); return 0 }
22
23// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
24// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
25// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
26// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
27func cf_pi(v: i64) -> i64 { nxi_out(v); return 0 }
28
29func cf_cat(dst: *u8, off: i64, s: *u8) -> i64 {
30 var i: i64 = 0
31 while s[i] != (0 as u8) { dst[off+i] = s[i]; i = i + 1 }
32 return off + i
33}
34
35func cf_catn(dst: *u8, off: i64, v: i64) -> i64 {
36 let t: *u8 = sys_mmap(32)
37 var m: i64 = v
38 var k: i64 = 0
39 if m == 0 { t[0] = 48 as u8; k = 1 }
40 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
41 var i: i64 = 0
42 while i < k { dst[off+i] = t[k-1-i]; i = i + 1 }
43 return off + k
44}
45
46// n bytes -> 2n lowercase hex into out, NUL-terminated
47func cf_hexn(d: *u8, n: i64, out: *u8) -> i64 {
48 let hx: *u8 = "0123456789abcdef" as *u8
49 var i: i64 = 0
50 while i < n {
51 let b: i64 = d[i] as i64
52 out[i*2] = hx[(b >> 4) & 15]
53 out[i*2+1] = hx[b & 15]
54 i = i + 1
55 }
56 out[n*2] = 0 as u8
57 return n*2
58}
59
60func cf_nib(c: i64) -> i64 {
61 if c >= 97 { return c - 87 }
62 return c - 48
63}
64
65// 2n hex chars in -> n bytes into out
66func cf_unhex(hexin: *u8, hexlen: i64, out: *u8) -> i64 {
67 var i: i64 = 0
68 let n: i64 = hexlen / 2
69 while i < n {
70 let hi: i64 = cf_nib(hexin[i*2] as i64)
71 let lo: i64 = cf_nib(hexin[i*2+1] as i64)
72 out[i] = ((hi * 16) + lo) as u8
73 i = i + 1
74 }
75 return n
76}
77
78func cf_hash_hex(buf: *u8, n: i64, out: *u8) -> i64 {
79 let dg: *u8 = sys_mmap(32)
80 sha256_digest(buf, n, dg)
81 cf_hexn(dg, 32, out)
82 return 64
83}
84
85func cf_eq(a: *u8, b: *u8) -> i64 {
86 var i: i64 = 0
87 while a[i] != (0 as u8) {
88 if a[i] != b[i] { return 0 }
89 i = i + 1
90 }
91 if b[i] != (0 as u8) { return 0 }
92 return 1
93}
94
95func cf_copy(dst: *u8, src: *u8) -> i64 {
96 var i: i64 = 0
97 while src[i] != (0 as u8) { dst[i] = src[i]; i = i + 1 }
98 dst[i] = 0 as u8
99 return i
100}
101
102func cf_line_end(b: *u8, i0: i64, n: i64) -> i64 {
103 var e: i64 = i0
104 while e < n { if b[e] == (10 as u8) { return e } e = e + 1 }
105 return n
106}
107
108func cf_find_tab(b: *u8, i0: i64, e: i64) -> i64 {
109 var j: i64 = i0
110 while j < e { if b[j] == (9 as u8) { return j } j = j + 1 }
111 return e
112}
113
114func cf_slice(b: *u8, i0: i64, e: i64, dst: *u8) -> i64 {
115 var o: i64 = 0
116 var j: i64 = i0
117 while j < e { dst[o] = b[j]; o = o + 1; j = j + 1 }
118 dst[o] = 0 as u8
119 return o
120}
121
122// dedicated CoE signing seed (deterministic v1; distinct from the platform key)
123func cf_coe_seed(out: *u8) -> i64 {
124 var i: i64 = 0
125 while i < 32 { out[i] = ((i * 11 + 29) & 0xff) as u8; i = i + 1 }
126 return 0
127}
128
129// scan plane [0,n): copy last row's frame-hash (field 0) into lastfh (or GENESIS); return row count
130func cf_scan_last(vb: *u8, n: i64, lastfh: *u8) -> i64 {
131 cf_copy(lastfh, "GENESIS" as *u8)
132 var i: i64 = 0
133 var rows: i64 = 0
134 while i < n {
135 let e: i64 = cf_line_end(vb, i, n)
136 if e > i {
137 let t: i64 = cf_find_tab(vb, i, e)
138 if t < e { cf_slice(vb, i, t, lastfh); rows = rows + 1 }
139 }
140 i = e + 1
141 }
142 return rows
143}
144
145func main() -> i64 {
146 cf_puts("=== COE-FRAME v1 (F701+F702): ed25519-signed 5-pillar CID hash-chain ===\n" as *u8)
147 let plane: *u8 = "knowledge/status/coe_signed.log" as *u8
148
149 let seed: *u8 = sys_mmap(32)
150 cf_coe_seed(seed)
151 let pub: *u8 = sys_mmap(32)
152 ed25519_pub_from_priv(seed, pub)
153 let pubhex: *u8 = sys_mmap(72)
154 cf_hexn(pub, 32, pubhex)
155
156 let lenp: *i64 = sys_mmap(16) as *i64
157 lenp[0] = 0
158 let buf: *u8 = sys_read_file(plane, lenp)
159 var n: i64 = lenp[0]
160 if n < 0 { n = 0 }
161 if n > CF_CAP { n = CF_CAP }
162
163 let prevfh: *u8 = sys_mmap(80)
164 let seq: i64 = cf_scan_last(buf, n, prevfh)
165
166 let now: i64 = sys_now_realtime_sec()
167 let pay: *u8 = sys_mmap(256)
168 var po: i64 = 0
169 po = cf_cat(pay, po, "coe-selftest-payload-" as *u8)
170 po = cf_catn(pay, po, now)
171 pay[po] = 0 as u8
172 let cid: *u8 = sys_mmap(80)
173 cf_hash_hex(pay, po, cid)
174
175 // record = prev \t cid \t seq \t now \t pillars ; fh = sha256(record) ; sig = ed25519(record)
176 let rec: *u8 = sys_mmap(CF_MAGIC_1024)
177 var ro: i64 = 0
178 ro = cf_cat(rec, ro, prevfh)
179 rec[ro] = 9 as u8; ro = ro + 1
180 ro = cf_cat(rec, ro, cid)
181 rec[ro] = 9 as u8; ro = ro + 1
182 ro = cf_catn(rec, ro, seq)
183 rec[ro] = 9 as u8; ro = ro + 1
184 ro = cf_catn(rec, ro, now)
185 rec[ro] = 9 as u8; ro = ro + 1
186 ro = cf_cat(rec, ro, "prov=organ:nx_coe_frame;cust=cap;env=nas;corr=rehash+ed25519" as *u8)
187 rec[ro] = 0 as u8
188
189 let fh: *u8 = sys_mmap(80)
190 cf_hash_hex(rec, ro, fh)
191 let sig: *u8 = sys_mmap(64)
192 ed25519_sign_full(seed, rec, ro, sig)
193 let sighex: *u8 = sys_mmap(160)
194 cf_hexn(sig, 64, sighex)
195
196 // line = fh \t sighex \t rec \n
197 let line: *u8 = sys_mmap(CF_MAGIC_1500)
198 var lo: i64 = 0
199 lo = cf_cat(line, lo, fh)
200 line[lo] = 9 as u8; lo = lo + 1
201 lo = cf_cat(line, lo, sighex)
202 line[lo] = 9 as u8; lo = lo + 1
203 lo = cf_cat(line, lo, rec)
204 line[lo] = 10 as u8; lo = lo + 1
205
206 let fd: i64 = sys_openat_append(plane, 0x1a4)
207 if fd < 0 { cf_puts("COE-FRAME RED reason=no-plane\n" as *u8); sys_exit(1); return 1 }
208 sys_write(fd, line, lo)
209 sys_close(fd)
210 cf_puts(" emitted seq=" as *u8); cf_pi(seq)
211 cf_puts(" cid=" as *u8); cf_puts(cid)
212 cf_puts(" frame=" as *u8); cf_puts(fh)
213 cf_puts(" sig=" as *u8); cf_puts(sighex); cf_puts("\n" as *u8)
214
215 // --- VERIFY whole chain: integrity (re-hash) + authenticity (ed25519) + linkage ---
216 lenp[0] = 0
217 let vb: *u8 = sys_read_file(plane, lenp)
218 var vn: i64 = lenp[0]
219 if vn < 0 { vn = 0 }
220 var truncated: i64 = 0
221 if vn > CF_CAP { vn = CF_CAP; truncated = 1 }
222
223 let sfh: *u8 = sys_mmap(80)
224 let rfh: *u8 = sys_mmap(80)
225 let shx: *u8 = sys_mmap(160)
226 let sbytes: *u8 = sys_mmap(64)
227 let pfld: *u8 = sys_mmap(80)
228 let carry: *u8 = sys_mmap(80)
229 cf_copy(carry, "GENESIS" as *u8)
230 var i: i64 = 0
231 var rows: i64 = 0
232 var okhash: i64 = 1
233 var oksig: i64 = 1
234 var oklink: i64 = 1
235 var capped: i64 = 0
236 while i < vn {
237 let e: i64 = cf_line_end(vb, i, vn)
238 if e > i {
239 if rows < CF_MAXROWS {
240 let t1: i64 = cf_find_tab(vb, i, e)
241 if t1 < e {
242 let t2: i64 = cf_find_tab(vb, t1 + 1, e)
243 if t2 < e {
244 cf_slice(vb, i, t1, sfh)
245 cf_slice(vb, t1 + 1, t2, shx)
246 let rs: i64 = t2 + 1
247 let reclen: i64 = e - rs
248 let recp: *u8 = ((vb as i64) + rs) as *u8
249 cf_hash_hex(recp, reclen, rfh)
250 if cf_eq(sfh, rfh) == 0 { okhash = 0 }
251 cf_unhex(shx, 128, sbytes)
252 if ed25519_verify_full(pub, recp, reclen, sbytes) == NX_ED25519_SIG_OK {
253 } else { oksig = 0 }
254 let pt: i64 = cf_find_tab(vb, rs, e)
255 cf_slice(vb, rs, pt, pfld)
256 if cf_eq(pfld, carry) == 0 { oklink = 0 }
257 cf_copy(carry, sfh)
258 rows = rows + 1
259 }
260 }
261 }
262 if rows >= CF_MAXROWS { capped = 1 }
263 }
264 i = e + 1
265 }
266
267 cf_puts(" pubkey=" as *u8); cf_puts(pubhex); cf_puts("\n" as *u8)
268 if okhash == 1 {
269 if oksig == 1 {
270 if oklink == 1 {
271 cf_puts("COE-FRAME GREEN frames=" as *u8); cf_pi(rows)
272 cf_puts(" chain=OK signed=ed25519 sig-verify=OK bytes=" as *u8); cf_pi(vn)
273 cf_puts(" envelope=cap:" as *u8); cf_pi(CF_CAP)
274 cf_puts("/maxrows:" as *u8); cf_pi(CF_MAXROWS)
275 if truncated == 1 { cf_puts(" TRUNCATED" as *u8) }
276 if capped == 1 { cf_puts(" ROWCAP" as *u8) }
277 cf_puts("\n" as *u8)
278 return 0
279 }
280 }
281 }
282 cf_puts("COE-FRAME RED hash=" as *u8); cf_pi(okhash)
283 cf_puts(" sig=" as *u8); cf_pi(oksig)
284 cf_puts(" link=" as *u8); cf_pi(oklink)
285 cf_puts(" frames=" as *u8); cf_pi(rows); cf_puts("\n" as *u8)
286 sys_exit(1)
287 return 1
288}