code wiki / (root) / nx_asset_merkle_gate.nx

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}