code wiki / (root) / nx_asset_provenance_gate.nx

nx_asset_provenance_gate.nx source

↩ module page · 239 lines · 14434 B

1// nx_asset_provenance_gate.nx -- KAT + TEETH for nx_asset_provenance (R2 of the universal org-tooling arc). 2// 3// Proves CRYPTOGRAPHICALLY SIGNED, tamper-evident provenance: we can TRULY KNOW an artifact's class 4// (generated image / downloaded video / human-authored) because the credential is ed25519-SIGNED and 5// BOUND (via the W3C-PROV `entity` = the asset's record CID) to that exact content. DENY-BY-DEFAULT. 6// 7// ANCHOR RFC 8032 §7.1 TEST 1 KAT the SAME ed25519 we sign with derives the published public key 8// byte-for-byte -> the crypto base is GENUINE (RFC 8032), not a 9// stub and not rolled here (reused from nx_ed25519_signature). 10// (a) VALID a credential signed by key K verifies against pubkey K for its record CID -> ALLOW 11// (b) TAMPER flip one byte of the assertion inside the credential -> REJECT 12// (c) FORGE sign with key K2, verify against trusted pubkey K1 (what a plain hash can't catch) 13// -> REJECT 14// (d) REBIND a credential minted for record A, verified with expect_record_cid = B (binding) 15// -> REJECT 16// (e) 3 CLASSES human / machine(tool+model) / downloaded(source URL) each sign+verify ALLOW and 17// carry their payload in the assertion. 18// 19// Verdict logged to knowledge/status/asset_provenance_gate.log (append-only; ADDITIVE law #13). 20// expect_exit: 0 license_tier: ORIGINAL 21import "nx_syscalls.nx" 22import "nx_canon_cid.nx" 23import "nx_uxf_decode.nx" 24import "nx_ed25519_signature.nx" 25import "nx_asset_provenance.nx" 26import "nx_gate_verdict.nx" 27 28// log fd THREADED as a parameter (compiler supports const globals but not reassigning a module-level 29// var from inside a function -- match the R0 nx_asset_record_gate idiom). 30func g_puts(logfd: i64, s: *u8) -> i64 { 31 var n: i64 = 0 32 while s[n] != (0 as u8) { n = n + 1 } 33 sys_write(1, s, n) 34 if logfd > 0 { sys_write(logfd, s, n) } 35 return 0 36} 37func g_putn(logfd: i64, v: i64) -> i64 { 38 let bb: *u8 = sys_mmap(28) 39 var m: i64 = v 40 if m < 0 { g_puts(logfd, "-\x00" as *u8); m = 0 - m } 41 let t: *u8 = sys_mmap(28) 42 var k: i64 = 0 43 if m == 0 { t[0] = 48 as u8; k = 1 } 44 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 45 var i: i64 = 0 46 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } 47 sys_write(1, bb, k) 48 if logfd > 0 { sys_write(logfd, bb, k) } 49 return 0 50} 51func g_streq(a: *u8, b: *u8) -> i64 { 52 var i: i64 = 0 53 while 1 == 1 { 54 if a[i] != b[i] { return 0 } 55 if a[i] == (0 as u8) { return 1 } 56 i = i + 1 57 } 58 return 1 59} 60 61// hex helpers to load the RFC 8032 test vector (proves the ed25519 is the real spec). 62func g_hb(c: i64) -> i64 { 63 if c >= 0x30 { if c <= 0x39 { return c - 0x30 } } 64 if c >= 0x61 { if c <= 0x66 { return c - 0x61 + 10 } } 65 if c >= 0x41 { if c <= 0x46 { return c - 0x41 + 10 } } 66 return 0 67} 68func g_hex2bytes(hex: *u8, nbytes: i64, out: *u8) -> i64 { 69 var i: i64 = 0 70 while i < nbytes { let hi: i64 = g_hb(hex[i*2] as i64); let lo: i64 = g_hb(hex[i*2+1] as i64); out[i] = ((hi << 4) | lo) as u8; i = i + 1 } 71 return 0 72} 73func g_byteeq(a: *u8, b: *u8, n: i64) -> i64 { 74 var i: i64 = 0 75 while i < n { if a[i] != b[i] { return 0 } i = i + 1 } 76 return 1 77} 78// empty-string sentinel for omitted optional fields (mirrors R0's E()). 79func E() -> *u8 { return "\x00" as *u8 } 80 81func main() -> i64 { 82 let logfd: i64 = sys_openat_append("knowledge/status/asset_provenance_gate.log\x00" as *u8, 0x1a4) 83 g_puts(logfd, "=== ASSET-PROVENANCE-GATE (R2: signed, tamper-evident, bound provenance) ===\n\x00" as *u8) 84 85 var pass: i64 = 0 86 var total: i64 = 0 87 88 // ============================================================================================ 89 // ANCHOR: RFC 8032 §7.1 TEST 1 -- our signing key (K1) IS the published test seed, and the 90 // public key our ed25519 derives MUST equal the published vector (proves genuine RFC-8032 crypto, 91 // reused not rolled). This same K1 signs the credentials below. 92 let priv1: *u8 = sys_mmap(32) 93 g_hex2bytes("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60\x00" as *u8, 32, priv1) 94 let exp_pub: *u8 = sys_mmap(32) 95 g_hex2bytes("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a\x00" as *u8, 32, exp_pub) 96 let pub1: *u8 = sys_mmap(32) 97 ed25519_pub_from_priv(priv1, pub1) 98 total = total + 1 99 g_puts(logfd, " (KAT) ed25519 genuine: pub_from_priv == RFC 8032 test-1 vector: \x00" as *u8) 100 if g_byteeq(pub1, exp_pub, 32) == 1 { pass = pass + 1; g_puts(logfd, "PASS\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) } 101 102 // A second, DIFFERENT key K2 (the attacker / wrong signer) for the FORGE test. 103 let priv2: *u8 = sys_mmap(32) 104 g_hex2bytes("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20\x00" as *u8, 32, priv2) 105 let pub2: *u8 = sys_mmap(32) 106 ed25519_pub_from_priv(priv2, pub2) 107 108 // ============================================================================================ 109 // A representative asset RECORD CID (record A). In production this comes from ar_cid over the 110 // record bytes; here we use a fixed, well-formed CID-shaped string -- the gate proves the 111 // provenance machinery, the CID's own integrity is R0's gate. 112 let cidA: *u8 = "nxc1-1111111111111111111111111111111111111111111111111111111111111111\x00" as *u8 113 let cidB: *u8 = "nxc1-2222222222222222222222222222222222222222222222222222222222222222\x00" as *u8 114 115 // ---- (a) VALID: machine-generated-image credential signed by K1 verifies for cidA ---- 116 let asrtA: *u8 = sys_mmap(4096) 117 let nA: i64 = prov_make(cidA, prov_class_machine(), "software\x00" as *u8, 118 "nishi-gen-img\x00" as *u8, "sdxl-v1\x00" as *u8, E(), E(), asrtA) 119 let credA: *u8 = sys_mmap(4096) 120 let clenA: i64 = prov_sign(asrtA, nA, priv1, credA) 121 g_puts(logfd, " credA: assertion_bytes=\x00" as *u8); g_putn(logfd, nA); g_puts(logfd, " credential_bytes=\x00" as *u8); g_putn(logfd, clenA); g_puts(logfd, "\n\x00" as *u8) 122 total = total + 1 123 g_puts(logfd, " (a) VALID: K1-signed credential verifies under pubkey K1 for its CID -> ALLOW: \x00" as *u8) 124 let vA: i64 = prov_verify(credA, clenA, pub1, cidA) 125 if vA == 1 { pass = pass + 1; g_puts(logfd, "PASS (1)\n\x00" as *u8) } else { g_puts(logfd, "FAIL (\x00" as *u8); g_putn(logfd, vA); g_puts(logfd, ")\n\x00" as *u8) } 126 127 // ---- (b) TAMPER-ASSERTION: flip one byte of the assertion inside a copy of the credential ---- 128 let credT: *u8 = sys_mmap(4096) 129 var ci: i64 = 0 130 while ci < clenA { credT[ci] = credA[ci]; ci = ci + 1 } 131 // assertion bytes start at PROV_HDR (9); flip a byte in the middle of the assertion region. 132 let tpos: i64 = 9 + (nA / 2) 133 credT[tpos] = (credT[tpos] ^ (1 as u8)) as u8 134 total = total + 1 135 g_puts(logfd, " (b) TAMPER: flip assertion byte@\x00" as *u8); g_putn(logfd, tpos); g_puts(logfd, " -> REJECT: \x00" as *u8) 136 let vT: i64 = prov_verify(credT, clenA, pub1, cidA) 137 if vT == 0 { pass = pass + 1; g_puts(logfd, "PASS (0)\n\x00" as *u8) } else { g_puts(logfd, "FAIL (verified tampered!)\n\x00" as *u8) } 138 139 // ---- (c) FORGE: sign the SAME assertion with K2, verify against trusted pubkey K1 -> REJECT ---- 140 // This is precisely the attack a plain hash/checksum CANNOT catch (an attacker recomputes the 141 // hash over malicious content); only signature verification against the trusted key catches it. 142 let credF: *u8 = sys_mmap(4096) 143 let clenF: i64 = prov_sign(asrtA, nA, priv2, credF) 144 total = total + 1 145 g_puts(logfd, " (c) FORGE: K2-signed credential verified against trusted pubkey K1 -> REJECT: \x00" as *u8) 146 let vF: i64 = prov_verify(credF, clenF, pub1, cidA) 147 if vF == 0 { pass = pass + 1; g_puts(logfd, "PASS (0; sha256 could NOT catch this)\n\x00" as *u8) } else { g_puts(logfd, "FAIL (forgery verified!)\n\x00" as *u8) } 148 // ...and as a positive cross-check, the SAME forged credential DOES verify under K2's own pubkey 149 // (proves the rejection above is the KEY mismatch, not a malformed credential). 150 total = total + 1 151 g_puts(logfd, " (c2) the K2 credential verifies under K2's OWN pubkey (isolates key-mismatch): \x00" as *u8) 152 let vF2: i64 = prov_verify(credF, clenF, pub2, cidA) 153 if vF2 == 1 { pass = pass + 1; g_puts(logfd, "PASS (1)\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) } 154 155 // ---- (d) REBIND: credA was minted for cidA; verifying with expect_record_cid=cidB -> REJECT ---- 156 // The signature is perfectly valid, but the binding (assertion.entity == demanded CID) fails. 157 total = total + 1 158 g_puts(logfd, " (d) REBIND: credential for record A demanded against record B -> REJECT: \x00" as *u8) 159 let vR: i64 = prov_verify(credA, clenA, pub1, cidB) 160 if vR == 0 { pass = pass + 1; g_puts(logfd, "PASS (0; binding holds)\n\x00" as *u8) } else { g_puts(logfd, "FAIL (rebind accepted!)\n\x00" as *u8) } 161 162 // ============================================================================================ 163 // (e) 3 PROVENANCE CLASSES: each signs + verifies ALLOW and carries its class-specific payload. 164 165 // -- (e1) HUMAN-authored: agent = the person; no tool/model/source -- 166 let asrtH: *u8 = sys_mmap(4096) 167 let nH: i64 = prov_make(cidA, prov_class_human(), "Elder Westover\x00" as *u8, E(), E(), E(), E(), asrtH) 168 let credH: *u8 = sys_mmap(4096) 169 let clenH: i64 = prov_sign(asrtH, nH, priv1, credH) 170 let vH: i64 = prov_verify(credH, clenH, pub1, cidA) 171 // decode the assertion to confirm the carried payload 172 let dkh: *i64 = sys_mmap(8 * 16) as *i64 173 let dvh: *i64 = sys_mmap(8 * 16) as *i64 174 let nfh: i64 = canon_decode(asrtH, nH, dkh, dvh, 14) 175 total = total + 1 176 g_puts(logfd, " (e1) HUMAN signs+verifies ALLOW [class=\x00" as *u8); g_puts(logfd, prov_get(dkh, dvh, nfh, "prov_class\x00" as *u8)) 177 g_puts(logfd, " agent=\x00" as *u8); g_puts(logfd, prov_get(dkh, dvh, nfh, "prov_agent\x00" as *u8)); g_puts(logfd, "]: \x00" as *u8) 178 var h_ok: i64 = 0 179 if vH == 1 { 180 h_ok = 1 181 if g_streq(prov_get(dkh, dvh, nfh, "prov_class\x00" as *u8), "human\x00" as *u8) == 0 { h_ok = 0 } 182 if g_streq(prov_get(dkh, dvh, nfh, "prov_agent\x00" as *u8), "Elder Westover\x00" as *u8) == 0 { h_ok = 0 } 183 if ap_present(prov_get(dkh, dvh, nfh, "prov_tool\x00" as *u8)) == 1 { h_ok = 0 } // no tool 184 if ap_present(prov_get(dkh, dvh, nfh, "prov_source\x00" as *u8)) == 1 { h_ok = 0 } // no source 185 } 186 if h_ok == 1 { pass = pass + 1; g_puts(logfd, "PASS\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) } 187 188 // -- (e2) MACHINE-generated: tool + model carried (reuse credA, already verified) -- 189 let dkm: *i64 = sys_mmap(8 * 16) as *i64 190 let dvm: *i64 = sys_mmap(8 * 16) as *i64 191 let nfm: i64 = canon_decode(asrtA, nA, dkm, dvm, 14) 192 total = total + 1 193 g_puts(logfd, " (e2) MACHINE signs+verifies ALLOW [class=\x00" as *u8); g_puts(logfd, prov_get(dkm, dvm, nfm, "prov_class\x00" as *u8)) 194 g_puts(logfd, " tool=\x00" as *u8); g_puts(logfd, prov_get(dkm, dvm, nfm, "prov_tool\x00" as *u8)) 195 g_puts(logfd, " model=\x00" as *u8); g_puts(logfd, prov_get(dkm, dvm, nfm, "prov_model\x00" as *u8)); g_puts(logfd, "]: \x00" as *u8) 196 var m_ok: i64 = 0 197 if vA == 1 { 198 m_ok = 1 199 if g_streq(prov_get(dkm, dvm, nfm, "prov_class\x00" as *u8), "machine\x00" as *u8) == 0 { m_ok = 0 } 200 if g_streq(prov_get(dkm, dvm, nfm, "prov_tool\x00" as *u8), "nishi-gen-img\x00" as *u8) == 0 { m_ok = 0 } 201 if g_streq(prov_get(dkm, dvm, nfm, "prov_model\x00" as *u8), "sdxl-v1\x00" as *u8) == 0 { m_ok = 0 } 202 } 203 if m_ok == 1 { pass = pass + 1; g_puts(logfd, "PASS\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) } 204 205 // -- (e3) DOWNLOADED: source URL + fetch date carried; no tool/model -- 206 let asrtD: *u8 = sys_mmap(4096) 207 let nD: i64 = prov_make(cidA, prov_class_downloaded(), "external\x00" as *u8, E(), E(), 208 "https://example.com/clip.mp4\x00" as *u8, "2026-06-17\x00" as *u8, asrtD) 209 let credD: *u8 = sys_mmap(4096) 210 let clenD: i64 = prov_sign(asrtD, nD, priv1, credD) 211 let vD: i64 = prov_verify(credD, clenD, pub1, cidA) 212 let dkd: *i64 = sys_mmap(8 * 16) as *i64 213 let dvd: *i64 = sys_mmap(8 * 16) as *i64 214 let nfd: i64 = canon_decode(asrtD, nD, dkd, dvd, 14) 215 total = total + 1 216 g_puts(logfd, " (e3) DOWNLOADED signs+verifies ALLOW [class=\x00" as *u8); g_puts(logfd, prov_get(dkd, dvd, nfd, "prov_class\x00" as *u8)) 217 g_puts(logfd, " source=\x00" as *u8); g_puts(logfd, prov_get(dkd, dvd, nfd, "prov_source\x00" as *u8)); g_puts(logfd, "]: \x00" as *u8) 218 var d_ok: i64 = 0 219 if vD == 1 { 220 d_ok = 1 221 if g_streq(prov_get(dkd, dvd, nfd, "prov_class\x00" as *u8), "downloaded\x00" as *u8) == 0 { d_ok = 0 } 222 if g_streq(prov_get(dkd, dvd, nfd, "prov_source\x00" as *u8), "https://example.com/clip.mp4\x00" as *u8) == 0 { d_ok = 0 } 223 if g_streq(prov_get(dkd, dvd, nfd, "prov_date\x00" as *u8), "2026-06-17\x00" as *u8) == 0 { d_ok = 0 } 224 if ap_present(prov_get(dkd, dvd, nfd, "prov_tool\x00" as *u8)) == 1 { d_ok = 0 } // no tool 225 } 226 if d_ok == 1 { pass = pass + 1; g_puts(logfd, "PASS\n\x00" as *u8) } else { g_puts(logfd, "FAIL\n\x00" as *u8) } 227 228 // ============================================================================================ 229 g_puts(logfd, "ASSET-PROVENANCE-GATE passed \x00" as *u8); g_putn(logfd, pass); g_puts(logfd, "/\x00" as *u8); g_putn(logfd, total) 230 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 231 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 232 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 233 let ctr__dry: *i64 = gv_ctr() 234 ctr__dry[0] = pass 235 ctr__dry[1] = total 236 let rc__dry: i64 = gv_verdict("ASSET-PROVENANCE-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 237 sys_exit(rc__dry) 238 return rc__dry 239}