nx_asset_merkle_gate.nx source
↩ module page · 178 lines · 8593 B
1// nx_asset_merkle_gate.nx -- KAT + TEETH for nx_asset_merkle (R8: whole-inventory Merkle root).
2//
3// (T1) DETERMINISM / order-independent: root[A,B,C,D] == root[D,C,B,A] (same SET -> same root)
4// (T2) TAMPER: change one record's bytes -> the whole-inventory root CHANGES
5// (T3) COMPLETENESS: drop one record -> the root CHANGES (an omission cannot hide)
6// (T4) INCLUSION: a member's CID is a leaf; a well-formed but absent CID is NOT (no false inclusion)
7//
8// Verdict appended to knowledge/status/asset_merkle_gate.log (additive law #13).
9// expect_exit: 0 license_tier: ORIGINAL
10import "nx_syscalls.nx"
11import "nx_canon_cid.nx"
12import "nx_asset_record.nx"
13import "nx_asset_catalog.nx"
14import "nx_asset_merkle.nx"
15import "nx_gate_verdict.nx"
16
17func g_puts(logfd: i64, s: *u8) -> i64 {
18 var n: i64 = 0
19 while s[n] != (0 as u8) { n = n + 1 }
20 sys_write(1, s, n)
21 if logfd > 0 { sys_write(logfd, s, n) }
22 return 0
23}
24func g_putn(logfd: i64, v: i64) -> i64 {
25 let bb: *u8 = sys_mmap(28)
26 var m: i64 = v
27 if m < 0 { sys_write(1, "-\x00" as *u8, 1); m = 0 - m }
28 let t: *u8 = sys_mmap(28)
29 var k: i64 = 0
30 if m == 0 { t[0] = 48 as u8; k = 1 }
31 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
32 var i: i64 = 0
33 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
34 sys_write(1, bb, k)
35 if logfd > 0 { sys_write(logfd, bb, k) }
36 return 0
37}
38func g_streq(a: *u8, b: *u8) -> i64 {
39 var i: i64 = 0
40 while 1 == 1 {
41 if a[i] != b[i] { return 0 }
42 if a[i] == (0 as u8) { return 1 }
43 i = i + 1
44 }
45 return 1
46}
47func E() -> *u8 { return "\x00" as *u8 }
48func sset(a: *i64, i: i64, s: *u8) -> i64 { a[i] = s as i64; return 0 }
49
50// build an asset record: type / classification / tags / id. returns byte length.
51func mk_asset(typ: *u8, cls: *u8, tags: *u8, id: *u8, out: *u8) -> i64 {
52 let core: *i64 = sys_mmap(8 * 8) as *i64
53 sset(core, 0, typ); sset(core, 1, "Asset\x00" as *u8); sset(core, 2, E())
54 sset(core, 3, "2026-06-20\x00" as *u8); sset(core, 4, id); sset(core, 5, E()); sset(core, 6, E())
55 let pz: *i64 = sys_mmap(8 * 8) as *i64
56 sset(pz, 0, E()); sset(pz, 1, E()); sset(pz, 2, E()); sset(pz, 3, E()); sset(pz, 4, E()); sset(pz, 5, E())
57 let mz: *i64 = sys_mmap(8 * 8) as *i64
58 sset(mz, 0, E()); sset(mz, 1, E()); sset(mz, 2, E()); sset(mz, 3, E())
59 let oz: *i64 = sys_mmap(8 * 8) as *i64
60 sset(oz, 0, tags); sset(oz, 1, E()); sset(oz, 2, E()); sset(oz, 3, "1\x00" as *u8); sset(oz, 4, cls); sset(oz, 5, E())
61 let keys: *i64 = sys_mmap(8 * 48) as *i64
62 let vals: *i64 = sys_mmap(8 * 48) as *i64
63 let n: i64 = ar_fields(core, pz, mz, oz, keys, vals)
64 return ar_encode(keys, vals, n, out)
65}
66
67func gt_prefix(prefix: *u8, base: *u8, salt: i64) -> i64 {
68 var po: i64 = 0
69 while base[po] != (0 as u8) { prefix[po] = base[po]; po = po + 1 }
70 var m: i64 = sys_now_ms() + salt
71 let ds: *u8 = sys_mmap(28)
72 var k: i64 = 0
73 if m == 0 { ds[0] = 48 as u8; k = 1 }
74 while m > 0 { ds[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
75 var j: i64 = 0
76 while j < k { prefix[po] = ds[k - 1 - j]; po = po + 1; j = j + 1 }
77 prefix[po] = 45 as u8; po = po + 1
78 prefix[po] = 0 as u8
79 return po
80}
81
82// fill recs[]/lens[] with the canonical 4-record set A,B,C,D.
83func build_abcd(recs: *i64, lens: *i64) -> i64 {
84 let rA: *u8 = sys_mmap(8192); lens[0] = mk_asset("image\x00" as *u8, "professional\x00" as *u8, "t1\x00" as *u8, "m-a\x00" as *u8, rA); recs[0] = rA as i64
85 let rB: *u8 = sys_mmap(8192); lens[1] = mk_asset("doc\x00" as *u8, "professional\x00" as *u8, "t2\x00" as *u8, "m-b\x00" as *u8, rB); recs[1] = rB as i64
86 let rC: *u8 = sys_mmap(8192); lens[2] = mk_asset("video\x00" as *u8, "private\x00" as *u8, "t3\x00" as *u8, "m-c\x00" as *u8, rC); recs[2] = rC as i64
87 let rD: *u8 = sys_mmap(8192); lens[3] = mk_asset("audio\x00" as *u8, "public\x00" as *u8, "t4\x00" as *u8, "m-d\x00" as *u8, rD); recs[3] = rD as i64
88 return 4
89}
90
91// build a fresh catalog from recs[0..n) and return its Merkle root in out_root.
92func root_of(salt: i64, recs: *i64, lens: *i64, n: i64, out_root: *u8) -> i64 {
93 let prefix: *u8 = sys_mmap(256)
94 gt_prefix(prefix, "/tmp/mrkg-\x00" as *u8, salt)
95 let st: *i64 = sys_mmap(16) as *i64
96 var i: i64 = 0
97 while i < n { cat_ingest(prefix, recs[i] as *u8, lens[i], st); i = i + 1 }
98 return mr_root(prefix, out_root)
99}
100
101func gt_determinism(logfd: i64) -> i64 {
102 let recs: *i64 = sys_mmap(8 * 8) as *i64
103 let lens: *i64 = sys_mmap(8 * 8) as *i64
104 build_abcd(recs, lens)
105 let rev: *i64 = sys_mmap(8 * 8) as *i64
106 let rl: *i64 = sys_mmap(8 * 8) as *i64
107 rev[0] = recs[3]; rl[0] = lens[3]; rev[1] = recs[2]; rl[1] = lens[2]; rev[2] = recs[1]; rl[2] = lens[1]; rev[3] = recs[0]; rl[3] = lens[0]
108 let r1: *u8 = sys_mmap(128); root_of(1, recs, lens, 4, r1)
109 let r2: *u8 = sys_mmap(128); root_of(2, rev, rl, 4, r2)
110 g_puts(logfd, " (T1) determinism: root[A,B,C,D]==root[D,C,B,A] (order-independent): \x00" as *u8)
111 if g_streq(r1, r2) == 1 { g_puts(logfd, "PASS\n\x00" as *u8); return 1 }
112 g_puts(logfd, "FAIL\n\x00" as *u8); return 0
113}
114
115func gt_tamper(logfd: i64) -> i64 {
116 let recs: *i64 = sys_mmap(8 * 8) as *i64
117 let lens: *i64 = sys_mmap(8 * 8) as *i64
118 build_abcd(recs, lens)
119 let r1: *u8 = sys_mmap(128); root_of(3, recs, lens, 4, r1)
120 let rDp: *u8 = sys_mmap(8192); let nDp: i64 = mk_asset("audio\x00" as *u8, "public\x00" as *u8, "t4-TAMPERED\x00" as *u8, "m-d\x00" as *u8, rDp)
121 let tam: *i64 = sys_mmap(8 * 8) as *i64
122 let tl: *i64 = sys_mmap(8 * 8) as *i64
123 tam[0] = recs[0]; tl[0] = lens[0]; tam[1] = recs[1]; tl[1] = lens[1]; tam[2] = recs[2]; tl[2] = lens[2]; tam[3] = rDp as i64; tl[3] = nDp
124 let r3: *u8 = sys_mmap(128); root_of(4, tam, tl, 4, r3)
125 g_puts(logfd, " (T2) tamper one record -> whole-inventory root CHANGES: \x00" as *u8)
126 if g_streq(r1, r3) == 0 { g_puts(logfd, "PASS\n\x00" as *u8); return 1 }
127 g_puts(logfd, "FAIL\n\x00" as *u8); return 0
128}
129
130func gt_completeness(logfd: i64) -> i64 {
131 let recs: *i64 = sys_mmap(8 * 8) as *i64
132 let lens: *i64 = sys_mmap(8 * 8) as *i64
133 build_abcd(recs, lens)
134 let r1: *u8 = sys_mmap(128); root_of(5, recs, lens, 4, r1)
135 let r4: *u8 = sys_mmap(128); root_of(6, recs, lens, 3, r4) // only A,B,C
136 g_puts(logfd, " (T3) completeness: dropping one record -> root CHANGES (omission cannot hide): \x00" as *u8)
137 if g_streq(r1, r4) == 0 { g_puts(logfd, "PASS\n\x00" as *u8); return 1 }
138 g_puts(logfd, "FAIL\n\x00" as *u8); return 0
139}
140
141func gt_inclusion(logfd: i64) -> i64 {
142 let recs: *i64 = sys_mmap(8 * 8) as *i64
143 let lens: *i64 = sys_mmap(8 * 8) as *i64
144 build_abcd(recs, lens)
145 let p: *u8 = sys_mmap(256); gt_prefix(p, "/tmp/mrkg-inc-\x00" as *u8, 9)
146 let st: *i64 = sys_mmap(16) as *i64
147 var i: i64 = 0
148 while i < 4 { cat_ingest(p, recs[i] as *u8, lens[i], st); i = i + 1 }
149 let cidA: *u8 = sys_mmap(128); ar_cid(recs[0] as *u8, lens[0], cidA)
150 let bogus: *u8 = "nxc1-ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\x00" as *u8
151 var ok: i64 = 1
152 if mr_contains(p, cidA) != 1 { ok = 0 }
153 if mr_contains(p, bogus) != 0 { ok = 0 }
154 g_puts(logfd, " (T4) inclusion: member CID present, well-formed-but-absent CID NOT (no false inclusion): \x00" as *u8)
155 if ok == 1 { g_puts(logfd, "PASS\n\x00" as *u8); return 1 }
156 g_puts(logfd, "FAIL\n\x00" as *u8); return 0
157}
158
159func main() -> i64 {
160 let logfd: i64 = sys_openat_append("knowledge/status/asset_merkle_gate.log\x00" as *u8, 0x1a4)
161 g_puts(logfd, "=== ASSET-MERKLE-GATE (R8: whole-inventory Merkle root) ===\n\x00" as *u8)
162 var pass: i64 = 0
163 var total: i64 = 4
164 pass = pass + gt_determinism(logfd)
165 pass = pass + gt_tamper(logfd)
166 pass = pass + gt_completeness(logfd)
167 pass = pass + gt_inclusion(logfd)
168 g_puts(logfd, "ASSET-MERKLE-GATE passed \x00" as *u8); g_putn(logfd, pass); g_puts(logfd, "/\x00" as *u8); g_putn(logfd, total)
169 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
170 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
171 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
172 let ctr__dry: *i64 = gv_ctr()
173 ctr__dry[0] = pass
174 ctr__dry[1] = total
175 let rc__dry: i64 = gv_verdict("ASSET-MERKLE-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
176 sys_exit(rc__dry)
177 return rc__dry
178}