code wiki / _hdl_build / nx_attest_ceremony.nx

nx_attest_ceremony.nx source

↩ module page · 275 lines · 17087 B

1// nx_attest_ceremony.nx -- the OPERATOR-SIDE key ceremony for the attestation plane, PROVEN against 2// the real verifier before any real key exists. 3// 4// WHY: PROVEN=0/41 is bound on knowledge/attest_keys.conf (operator-held Ed25519 key). The recipe 5// existed only as PROSE derived from reading nx_evattest.nx -- and an untested recipe handed to the 6// operator is litter: if the claim-prefix boundary is off by one byte, every genuine attestation 7// reads BAD-SIGNATURE while all refusal paths still pass (nx_evattest.nx:136 warns exactly this). 8// This organ makes the ceremony EXECUTABLE and proves it byte-exact with a THROWAWAY key that is 9// never registered anywhere real -- the real key stays with the operator, never in an agent session. 10// 11// THE LINE THIS TOOL DOES NOT CROSS: it contains no path that writes knowledge/attest_keys.conf. 12// Registration is the operator's act (the whole clause, per nx_evattest: "Minting a human 13// attestation is an act the operator performs with a key the operator holds"). This tool only makes 14// that act one command and PROVES the output verifies. 15// 16// nx_attest_ceremony keygen <priv_out> -- 32B seed from /dev/urandom -> file; prints pub=<64hex> 17// (pub is PUBLIC; the priv file never prints) 18// nx_attest_ceremony sign <priv_file> <row_file> -- signs the row file's CLAIM PREFIX (trailing newline 19// stripped), prints the full signed row 20// nx_attest_ceremony -- GATE: 6 teeth against the REAL at_verify_row 21// license_tier: ORIGINAL layer: evidence module: nishi-core.evidence.attest_ceremony 22import "nx_evattest.nx" 23const K_MAGIC_8192: i64 = 8192 24const K_MAGIC_8000: i64 = 8000 25const K_MAGIC_65536: i64 = 65536 26const K_MAGIC_65000: i64 = 65000 27const K_MAGIC_4096: i64 = 4096 28const K_MAGIC_4000: i64 = 4000 29 30func ac_p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 31func ac_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 32func ac_hex(b: *u8, n: i64, out: *u8) -> i64 { 33 var i: i64 = 0 34 while i < n { 35 let v: i64 = b[i] as i64 36 let hi: i64 = (v >> 4) & 15 37 let lo: i64 = v & 15 38 if hi < 10 { out[i*2] = (48+hi) as u8 } else { out[i*2] = (87+hi) as u8 } 39 if lo < 10 { out[i*2+1] = (48+lo) as u8 } else { out[i*2+1] = (87+lo) as u8 } 40 i = i + 1 41 } 42 out[n*2] = 0 as u8 43 return n*2 44} 45// substring search within a row window (mirrors ss_row_has / at_find semantics) 46func ac_has(b: *u8, s: i64, e: i64, pat: *u8) -> i64 { 47 var pl: i64 = 0 48 while pat[pl] != (0 as u8) { pl = pl + 1 } 49 if pl == 0 { return 0 } 50 var i: i64 = s 51 while i + pl <= e { 52 var k: i64 = 0 53 var ok: i64 = 1 54 while k < pl { if b[i+k] != pat[k] { ok = 0; k = pl } else { k = k + 1 } } 55 if ok == 1 { return 1 } 56 i = i + 1 57 } 58 return 0 59} 60func ac_n(v: i64) -> i64 { 61 let t: *u8 = sys_mmap(64) 62 let o: *u8 = sys_mmap(64) 63 var m: i64=v; var k: i64=0 64 if m==0 { t[0]=48 as u8; k=1 } 65 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 66 var i: i64=0; while i<k { o[i]=t[k-1-i]; i=i+1 } 67 sys_write(1,o,k) 68 sys_munmap(t,64); sys_munmap(o,64) 69 return 0 70} 71func ac_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var i: i64=0; while s[i]!=(0 as u8){ dst[o]=s[i]; o=o+1; i=i+1 } return o } 72 73// 32 random bytes from the kernel CSPRNG. REFUSES on short read -- a partially random key is worse 74// than no key, and /dev/urandom short-reads are exactly the kind of silent failure this plane hates. 75func ac_random32(out: *u8) -> i64 { 76 let fd: i64 = sys_openat_rd("/dev/urandom" as *u8) 77 if fd < 0 { return 0 } 78 let n: i64 = sys_read(fd, out, 32) 79 sys_close(fd) 80 if n != 32 { return 0 } 81 return 1 82} 83 84// Sign `row` (claim prefix, exact bytes) with priv; append " pub=<64hex> sig=<128hex>" into out. 85// Returns total row length, or -1. 86func ac_sign_row(priv: *u8, row: *u8, rowlen: i64, out: *u8) -> i64 { 87 let pub: *u8 = sys_mmap(32) 88 if ed25519_pub_from_priv(priv, pub) != 0 { return 0 - 1 } 89 let sig: *u8 = sys_mmap(64) 90 if ed25519_sign_full(priv, row, rowlen, sig) != 0 { return 0 - 1 } 91 var o: i64 = 0 92 var i: i64 = 0 93 while i < rowlen { out[o] = row[i]; o = o + 1; i = i + 1 } 94 o = ac_cat(out, o, " pub=" as *u8) 95 let ph: *u8 = sys_mmap(80); ac_hex(pub, 32, ph); o = ac_cat(out, o, ph) 96 o = ac_cat(out, o, " sig=" as *u8) 97 let sh: *u8 = sys_mmap(160); ac_hex(sig, 64, sh); o = ac_cat(out, o, sh) 98 out[o] = 0 as u8 99 return o 100} 101 102func main(argc: i64, argv: *i64) -> i64 { 103 if argc >= 2 { 104 let mode: *u8 = argv[1] as *u8 105 // ---- keygen <priv_out> ---- 106 if mode[0] == (107 as u8) { 107 if argc < 3 { ac_p("usage: nx_attest_ceremony keygen <priv_out>\n" as *u8); sys_exit(2); return 2 } 108 let priv: *u8 = sys_mmap(32) 109 if ac_random32(priv) != 1 { ac_p("REFUSED: /dev/urandom short read -- no key written\n" as *u8); sys_exit(3); return 3 } 110 let fd: i64 = sys_openat_wr(argv[2] as *u8, 0x180) // 0600: owner rw, nobody else 111 if fd < 0 { ac_p("REFUSED: cannot write priv file\n" as *u8); sys_exit(4); return 4 } 112 sys_write(fd, priv, 32) 113 sys_close(fd) 114 let pub: *u8 = sys_mmap(32) 115 ed25519_pub_from_priv(priv, pub) 116 let ph: *u8 = sys_mmap(80); ac_hex(pub, 32, ph) 117 ac_p("priv written (32B raw, 0600). REGISTRY LINE (public -- paste into knowledge/attest_keys.conf):\n" as *u8) 118 ac_p("role=human pub=" as *u8); ac_p(ph); ac_p(" name=operator\n" as *u8) 119 sys_exit(0); return 0 120 } 121 // ---- check <row_file> <registry_file> -- verify a LIVE row against a LIVE registry ---- 122 // Added 2026-08-01: six hypotheses for "the signed oracle row does not count" were eliminated by 123 // measurement (require_human, join key, stamp cache, enum mismatch, binary drift, class-load skip) 124 // and the survivor is at_verify_row's verdict on the row itself. REASONING ABOUT A REFUSAL CODE IS 125 // NOT READING ONE -- this prints the code the board actually computes, from the same function. 126 if mode[0] == (99 as u8) { 127 if argc < 4 { ac_p("usage: nx_attest_ceremony check <row_file> <registry_file>\n" as *u8); sys_exit(2); return 2 } 128 let rb: *u8 = sys_mmap(K_MAGIC_8192) 129 let rf2: i64 = sys_openat_rd(argv[2] as *u8) 130 if rf2 < 0 { ac_p("REFUSED: cannot read row file\n" as *u8); sys_exit(4); return 4 } 131 var rn2: i64 = sys_read(rf2, rb, K_MAGIC_8000) 132 sys_close(rf2) 133 while rn2 > 0 { if rb[rn2-1] == (10 as u8) { rn2 = rn2 - 1 } else { if rb[rn2-1] == (13 as u8) { rn2 = rn2 - 1 } else { break } } } 134 let cb: *u8 = sys_mmap(K_MAGIC_65536) 135 let cf: i64 = sys_openat_rd(argv[3] as *u8) 136 if cf < 0 { ac_p("REFUSED: cannot read registry file\n" as *u8); sys_exit(4); return 4 } 137 let cn2: i64 = sys_read(cf, cb, K_MAGIC_65000) 138 sys_close(cf) 139 ac_p("row_bytes=" as *u8); ac_n(rn2); ac_p(" registry_bytes=" as *u8); ac_n(cn2); ac_p("\n" as *u8) 140 var cl: i64 = 0 141 if ac_has(rb, 0, rn2, "class=human" as *u8) == 1 { cl = AT_CLASS_HUMAN } 142 if ac_has(rb, 0, rn2, "class=llm" as *u8) == 1 { cl = AT_CLASS_LLM } 143 if ac_has(rb, 0, rn2, "class=mech" as *u8) == 1 { cl = AT_CLASS_MECH } 144 if ac_has(rb, 0, rn2, "class=oracle" as *u8) == 1 { cl = AT_CLASS_ORACLE } 145 // 2026-08-03: the parser predated the EXPERIENTIAL class -- a supervisor evclass row checked 146 // as parsed_class=0 and returned OK WITHOUT exercising the ran=/witness=/observed= demands 147 // (T7/T8 bind only under the right enum). A checker that verifies under the wrong class 148 // proves less than it prints. 149 if ac_has(rb, 0, rn2, "class=experiential" as *u8) == 1 { cl = AT_CLASS_EXPERIENTIAL } 150 ac_p("parsed_class=" as *u8); ac_n(cl); ac_p("\n" as *u8) 151 let rc2: i64 = at_verify_row(rb, 0, rn2, cl, cb, cn2) 152 ac_p("at_verify_row -> " as *u8); sys_write(1, at_err_name(rc2), ac_slen(at_err_name(rc2))) 153 ac_p(" (rc=" as *u8); ac_n(rc2); ac_p(")\n" as *u8) 154 if rc2 == AT_OK { sys_exit(0); return 0 } 155 sys_exit(1); return 1 156 } 157 // ---- sign <priv_file> <row_file> ---- 158 if mode[0] == (115 as u8) { 159 if argc < 4 { ac_p("usage: nx_attest_ceremony sign <priv_file> <row_file>\n" as *u8); sys_exit(2); return 2 } 160 let priv: *u8 = sys_mmap(32) 161 let pf: i64 = sys_openat_rd(argv[2] as *u8) 162 if pf < 0 { ac_p("REFUSED: cannot read priv file\n" as *u8); sys_exit(4); return 4 } 163 if sys_read(pf, priv, 32) != 32 { sys_close(pf); ac_p("REFUSED: priv file is not 32 raw bytes\n" as *u8); sys_exit(4); return 4 } 164 sys_close(pf) 165 let row: *u8 = sys_mmap(K_MAGIC_4096) 166 let rf: i64 = sys_openat_rd(argv[3] as *u8) 167 if rf < 0 { ac_p("REFUSED: cannot read row file\n" as *u8); sys_exit(4); return 4 } 168 var rn: i64 = sys_read(rf, row, K_MAGIC_4000) 169 sys_close(rf) 170 while rn > 0 { if row[rn-1] == (10 as u8) { rn = rn - 1 } else { if row[rn-1] == (13 as u8) { rn = rn - 1 } else { break } } } 171 if rn <= 0 { ac_p("REFUSED: empty row\n" as *u8); sys_exit(4); return 4 } 172 let out: *u8 = sys_mmap(K_MAGIC_8192) 173 let on: i64 = ac_sign_row(priv, row, rn, out) 174 if on < 0 { ac_p("REFUSED: sign failed\n" as *u8); sys_exit(5); return 5 } 175 sys_write(1, out, on) 176 ac_p("\n" as *u8) 177 sys_exit(0); return 0 178 } 179 } 180 181 // ---- GATE: the whole ceremony against the REAL verifier, THROWAWAY key ---- 182 var red: i64 = 0 183 let priv: *u8 = sys_mmap(32) 184 if ac_random32(priv) != 1 { ac_p("[RED] no CSPRNG -- cannot gate\n" as *u8); sys_exit(1); return 1 } 185 let pub: *u8 = sys_mmap(32) 186 ed25519_pub_from_priv(priv, pub) 187 let ph: *u8 = sys_mmap(80); ac_hex(pub, 32, ph) 188 189 // registry buffers (in-memory; NOTHING written to knowledge/) 190 let reg_h: *u8 = sys_mmap(256) 191 var rhn: i64 = ac_cat(reg_h, 0, "role=human pub=" as *u8); rhn = ac_cat(reg_h, rhn, ph); rhn = ac_cat(reg_h, rhn, " name=gate-throwaway\n" as *u8) 192 let reg_m: *u8 = sys_mmap(256) 193 var rmn: i64 = ac_cat(reg_m, 0, "role=machine pub=" as *u8); rmn = ac_cat(reg_m, rmn, ph); rmn = ac_cat(reg_m, rmn, " name=gate-throwaway\n" as *u8) 194 let reg_none: *u8 = sys_mmap(64) 195 var rnn: i64 = ac_cat(reg_none, 0, "# empty registry\n" as *u8) 196 197 // T1: signed human row + human-role registry -> AT_OK 198 let claim: *u8 = "class=human verdict=pass scope=gate-selftest signer=gate epoch=1785623000" as *u8 199 let rowbuf: *u8 = sys_mmap(K_MAGIC_8192) 200 let rl: i64 = ac_sign_row(priv, claim, ac_slen(claim), rowbuf) 201 var rc: i64 = at_verify_row(rowbuf, 0, rl, AT_CLASS_HUMAN, reg_h, rhn) 202 ac_p("T1 signed human row vs human-role registry: rc=" as *u8); sys_write(1, at_err_name(rc), ac_slen(at_err_name(rc))) 203 if rc == AT_OK { ac_p(" GREEN\n" as *u8) } else { ac_p(" [RED]\n" as *u8); red = red + 1 } 204 205 // T2 NEG: tamper ONE claim byte after signing -> BADSIG 206 let tam: *u8 = sys_mmap(K_MAGIC_8192) 207 var ti: i64 = 0; while ti < rl { tam[ti] = rowbuf[ti]; ti = ti + 1 } 208 tam[20] = (tam[20] as i64 + 1) as u8 // inside "verdict=pass" 209 rc = at_verify_row(tam, 0, rl, AT_CLASS_HUMAN, reg_h, rhn) 210 ac_p("T2 NEG tampered claim byte: rc=" as *u8); sys_write(1, at_err_name(rc), ac_slen(at_err_name(rc))) 211 if rc == AT_ERR_BADSIG { ac_p(" GREEN\n" as *u8) } else { ac_p(" [RED] tamper not caught\n" as *u8); red = red + 1 } 212 213 // T3 NEG: valid signature, key NOT in registry -> UNKNOWNKEY 214 rc = at_verify_row(rowbuf, 0, rl, AT_CLASS_HUMAN, reg_none, rnn) 215 ac_p("T3 NEG unregistered key: rc=" as *u8); sys_write(1, at_err_name(rc), ac_slen(at_err_name(rc))) 216 if rc == AT_ERR_UNKNOWNKEY { ac_p(" GREEN\n" as *u8) } else { ac_p(" [RED] stranger's valid sig accepted\n" as *u8); red = red + 1 } 217 218 // T4 NEG: machine-role key signing a HUMAN row -> ROLE refused (the whole clause) 219 rc = at_verify_row(rowbuf, 0, rl, AT_CLASS_HUMAN, reg_m, rmn) 220 ac_p("T4 NEG machine key on human row: rc=" as *u8); sys_write(1, at_err_name(rc), ac_slen(at_err_name(rc))) 221 if rc == AT_ERR_ROLE { ac_p(" GREEN\n" as *u8) } else { ac_p(" [RED] role separation broken\n" as *u8); red = red + 1 } 222 223 // T5 NEG: oracle row WITHOUT refdig -> NOTDERIVED even with a valid registered signature 224 let oc1: *u8 = "class=oracle verdict=pass scope=x ref=RFC-9807 gate=nx_opaque_rfc_kat signer=m epoch=1785623000" as *u8 225 let ob1: *u8 = sys_mmap(K_MAGIC_8192) 226 let ol1: i64 = ac_sign_row(priv, oc1, ac_slen(oc1), ob1) 227 rc = at_verify_row(ob1, 0, ol1, AT_CLASS_ORACLE, reg_m, rmn) 228 ac_p("T5 NEG oracle row missing refdig=: rc=" as *u8); sys_write(1, at_err_name(rc), ac_slen(at_err_name(rc))) 229 if rc == AT_ERR_NOTDERIVED { ac_p(" GREEN\n" as *u8) } else { ac_p(" [RED] underived oracle accepted\n" as *u8); red = red + 1 } 230 231 // T6: full oracle row (ref+refdig+gate), machine key -> AT_OK (independence lives in the reference) 232 let oc2: *u8 = "class=oracle verdict=pass scope=x ref=RFC-9807 refdig=06f941b4d7307a29 gate=nx_opaque_rfc_kat signer=m epoch=1785623000" as *u8 233 let ob2: *u8 = sys_mmap(K_MAGIC_8192) 234 let ol2: i64 = ac_sign_row(priv, oc2, ac_slen(oc2), ob2) 235 rc = at_verify_row(ob2, 0, ol2, AT_CLASS_ORACLE, reg_m, rmn) 236 ac_p("T6 derived oracle row, machine key: rc=" as *u8); sys_write(1, at_err_name(rc), ac_slen(at_err_name(rc))) 237 if rc == AT_OK { ac_p(" GREEN\n" as *u8) } else { ac_p(" [RED]\n" as *u8); red = red + 1 } 238 239 // T7 NEG: EXPERIENTIAL row with no witness= -> NOTDERIVED. THE LOAD-BEARING NEGATIVE CONTROL for the 240 // new class: without it, anything holding a machine key could mint "the world responded" over a gate 241 // that merely returned 0 -- which would make this class strictly WEAKER than the signature it replaces. 242 let ec1: *u8 = "class=experiential verdict=pass scope=dht-announce ran=nx_dht_announce observed=acked=8 signer=m epoch=1785623000" as *u8 243 let eb1: *u8 = sys_mmap(K_MAGIC_8192) 244 let el1: i64 = ac_sign_row(priv, ec1, ac_slen(ec1), eb1) 245 rc = at_verify_row(eb1, 0, el1, AT_CLASS_EXPERIENTIAL, reg_m, rmn) 246 ac_p("T7 NEG experiential row missing witness=: rc=" as *u8); sys_write(1, at_err_name(rc), ac_slen(at_err_name(rc))) 247 if rc == AT_ERR_NOTDERIVED { ac_p(" GREEN\n" as *u8) } else { ac_p(" [RED] witnessless experiential accepted\n" as *u8); red = red + 1 } 248 249 // T8 NEG: experiential row missing observed= -> NOTDERIVED. "It worked" is a summary, not a measurement. 250 let ec2: *u8 = "class=experiential verdict=pass scope=dht-announce ran=nx_dht_announce witness=public-dht-peers signer=m epoch=1785623000" as *u8 251 let eb2: *u8 = sys_mmap(K_MAGIC_8192) 252 let el2: i64 = ac_sign_row(priv, ec2, ac_slen(ec2), eb2) 253 rc = at_verify_row(eb2, 0, el2, AT_CLASS_EXPERIENTIAL, reg_m, rmn) 254 ac_p("T8 NEG experiential row missing observed=: rc=" as *u8); sys_write(1, at_err_name(rc), ac_slen(at_err_name(rc))) 255 if rc == AT_ERR_NOTDERIVED { ac_p(" GREEN\n" as *u8) } else { ac_p(" [RED] unmeasured experiential accepted\n" as *u8); red = red + 1 } 256 257 // T9: full experiential row (ran+witness+observed), machine key -> AT_OK. Independence lives in the 258 // RESPONDENT: strangers' DHT nodes ACKed us; no signature could manufacture that. 259 let ec3: *u8 = "class=experiential verdict=pass scope=dht-announce ran=nx_dht_announce witness=public-dht-peers observed=acked=8 signer=m epoch=1785623000" as *u8 260 let eb3: *u8 = sys_mmap(K_MAGIC_8192) 261 let el3: i64 = ac_sign_row(priv, ec3, ac_slen(ec3), eb3) 262 rc = at_verify_row(eb3, 0, el3, AT_CLASS_EXPERIENTIAL, reg_m, rmn) 263 ac_p("T9 full experiential row, machine key: rc=" as *u8); sys_write(1, at_err_name(rc), ac_slen(at_err_name(rc))) 264 if rc == AT_OK { ac_p(" GREEN\n" as *u8) } else { ac_p(" [RED]\n" as *u8); red = red + 1 } 265 266 // T10 NEG: an UNREGISTERED key cannot mint an experiential row either -- the class is machine-signable, 267 // NOT unsigned-writable. Guards the obvious bypass: anything able to write knowledge/status/. 268 rc = at_verify_row(eb3, 0, el3, AT_CLASS_EXPERIENTIAL, reg_none, rnn) 269 ac_p("T10 NEG experiential, unregistered key: rc=" as *u8); sys_write(1, at_err_name(rc), ac_slen(at_err_name(rc))) 270 if rc == AT_ERR_UNKNOWNKEY { ac_p(" GREEN\n" as *u8) } else { ac_p(" [RED] unregistered experiential accepted\n" as *u8); red = red + 1 } 271 272 if red == 0 { ac_p("NX-ATTEST-CEREMONY 10/10 GREEN -- the recipe verifies against the REAL at_verify_row\n" as *u8); sys_exit(0); return 0 } 273 ac_p("NX-ATTEST-CEREMONY RED\n" as *u8) 274 sys_exit(1); return 1 275}