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}