code wiki / _hdl_build / nx_ed25519_extvec_gate.nx

nx_ed25519_extvec_gate.nx source

↩ module page · 201 lines · 8927 B

1// nx_ed25519_extvec_gate.nx -- SECOND provably third-party-validated claim: Ed25519 vs RFC 8032 7.1. 2// 3// Same construction as nx_x25519_extvec_gate, applied to a different authority to prove the pattern 4// GENERALISES rather than being a one-off: 5// 1 no expected value appears in this source -- the public key, message and signature are read at run 6// time out of knowledge/extvec/rfc8032.txt; 7// 2 that file is PINNED to the sha256 nx_vecfetch computed IN-PROCESS at the socket, before the bytes 8// touched disk (e3705131.., independently confirmed by GNU coreutils sha256sum); 9// 3 only then does ed25519_verify_full() run against the document's own signature. 10// ★The pin is the security property. Parsing from a file is necessary but not sufficient -- an edited file 11// would let us grade ourselves against answers of our own choosing. Pinning to an ACQUISITION-time digest 12// is what makes it evidence. 13// 14// SCOPE STATED HONESTLY: this proves our Ed25519 VERIFIER accepts the IETF's own signature over the IETF's 15// own message under the IETF's own public key. It does NOT prove our SIGNER produces those bytes -- that is 16// a separate claim needing the secret-key vector, and it is deliberately not asserted here. 17// license_tier: ORIGINAL expect_exit: 0 18import "nx_syscalls.nx" 19import "nx_sha256_wasm.nx" 20import "nx_ed25519_signature.nx" 21import "nx_gate_verdict.nx" 22 23func w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 24func wb(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 } 25 26func nn(v: i64) -> i64 { 27 var m: i64 = v 28 if m < 0 { w("-" as *u8); m = 0 - m } 29 let t: *u8 = sys_mmap(32) 30 var k: i64 = 0 31 if m == 0 { t[0] = 48 as u8; k = 1 } 32 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 33 let b: *u8 = sys_mmap(32) 34 var j: i64 = 0 35 while j < k { b[j] = t[k - 1 - j]; j = j + 1 } 36 sys_write(1, b, k) 37 return 0 38} 39 40func hexnib(v: i64) -> i64 { if v < 10 { return 48 + v } return 87 + v } 41 42func hexval(c: i64) -> i64 { 43 if c >= 48 { if c <= 57 { return c - 48 } } 44 if c >= 97 { if c <= 102 { return c - 87 } } 45 if c >= 65 { if c <= 70 { return c - 55 } } 46 return 0 - 1 47} 48 49func starts(b: *u8, n: i64, at: i64, s: *u8) -> i64 { 50 var i: i64 = 0 51 while s[i] != (0 as u8) { 52 if at + i >= n { return 0 } 53 if b[at + i] != s[i] { return 0 } 54 i = i + 1 55 } 56 return 1 57} 58 59func findfrom(b: *u8, n: i64, s: *u8, from: i64) -> i64 { 60 var p: i64 = from 61 while p < n { 62 if starts(b, n, p, s) == 1 { return p } 63 p = p + 1 64 } 65 return 0 - 1 66} 67 68// Decode exactly `want` bytes of hex starting at the first hex run at/after `from`. 69// STRICT: a short run returns -1 rather than a partial value -- a truncated key or signature must fail 70// loudly, never be compared as if complete. 71func parsehex(b: *u8, n: i64, from: i64, out: *u8, want: i64) -> i64 { 72 // ⚠RFC 8032 WRAPS EACH VALUE ACROSS TWO 32-CHAR LINES. My first cut reset the accumulator on ANY 73 // non-hex byte -- which the newline between those lines is -- so a 64-hex key parsed as "short/absent" 74 // and the gate went RED against a perfectly good document. ★The RED was honest and the parser was wrong: 75 // a strict rule that cannot read the authority's own layout reports its own blindness as the source's 76 // defect. Fix: WHITESPACE CONTINUES a run; any other non-hex byte still resets it, so the strictness 77 // that rejects genuinely short values is preserved. 78 var p: i64 = from 79 var got: i64 = 0 80 while p < n { 81 if got >= want { return p } 82 let c0: i64 = b[p] as i64 83 var ws: i64 = 0 84 if c0 == 32 { ws = 1 } 85 if c0 == 10 { ws = 1 } 86 if c0 == 13 { ws = 1 } 87 if c0 == 9 { ws = 1 } 88 let hv: i64 = hexval(c0) 89 if ws == 1 { p = p + 1 } 90 else { if hv < 0 { 91 if got > 0 { if got < want { got = 0 } } 92 p = p + 1 93 } else { 94 if p + 1 >= n { return 0 - 1 } 95 let hv2: i64 = hexval(b[p + 1] as i64) 96 if hv2 < 0 { p = p + 1 } 97 else { 98 out[got] = ((hv * 16) + hv2) as u8 99 got = got + 1 100 p = p + 2 101 } 102 } } 103 } 104 if got >= want { return p } 105 return 0 - 1 106} 107 108func main() -> i64 { 109 w("nx_ed25519_extvec_gate -- Ed25519 vs RFC 8032 7.1, vectors READ FROM THE FETCHED DOCUMENT\n" as *u8) 110 111 let lp: *i64 = sys_mmap(16) as *i64 112 lp[0] = 0 113 let b: *u8 = sys_read_file("knowledge/extvec/rfc8032.txt\x00" as *u8, lp) 114 if lp[0] <= 0 { 115 w("RED: fetched vector file absent -- run nx_vecfetch. NOT falling back to constants.\n" as *u8) 116 return 1 117 } 118 119 // ---- CLAUSE 1: PIN THE ACQUISITION ---- 120 let ctx: *u8 = sys_mmap(1024) 121 let dig: *u8 = sys_mmap(64) 122 nx_sha256_one_shot(b, lp[0], ctx, dig) 123 let hx: *u8 = sys_mmap(80) 124 var i: i64 = 0 125 while i < 32 { 126 hx[i * 2] = hexnib(((dig[i] as i64) / 16) & 15) as u8 127 hx[i * 2 + 1] = hexnib((dig[i] as i64) & 15) as u8 128 i = i + 1 129 } 130 // LOCAL literal, never a module-level `const *u8` -- those mis-compare in nx_cc (proven 2026-07-31: 131 // the digest printed byte-identical and the comparison still said FAILED). 132 let want: *u8 = "ed63657ff389301282b169b0abde9b5dd2c7e4d524fdfa5da6ff3094fc93c4c3\x00" as *u8 133 var pin: i64 = 1 134 i = 0 135 while i < 64 { if hx[i] != want[i] { pin = 0 } i = i + 1 } 136 w(" acquisition digest: " as *u8); wb(hx, 64); w("\n" as *u8) 137 if pin == 0 { 138 w("RED: PIN FAILED -- this file is not the one nx_vecfetch acquired. Refusing to read vectors\n" as *u8) 139 w(" from an unpinned document.\n" as *u8) 140 return 1 141 } 142 w(" PIN OK -- bytes match the digest computed in-process at the socket\n" as *u8) 143 144 // ---- CLAUSE 2: TEST 1 of 7.1 -- the empty-message vector, parsed out of the document ---- 145 // Chosen deliberately: MESSAGE is zero bytes, so there is no message-hex parsing step that could 146 // silently mis-slice and turn a real failure into a pass. 147 let t1: i64 = findfrom(b, lp[0], "-----TEST 1" as *u8, 0) 148 if t1 < 0 { w("RED: could not locate '-----TEST 1' in the document\n" as *u8); return 1 } 149 150 let lpub: i64 = findfrom(b, lp[0], "PUBLIC KEY:" as *u8, t1) 151 let pub: *u8 = sys_mmap(64) 152 let p1: i64 = parsehex(b, lp[0], lpub + 11, pub, 32) 153 let lsig: i64 = findfrom(b, lp[0], "SIGNATURE:" as *u8, t1) 154 let sig: *u8 = sys_mmap(96) 155 let p2: i64 = parsehex(b, lp[0], lsig + 10, sig, 64) 156 157 if lpub < 0 { w("RED: no PUBLIC KEY label\n" as *u8); return 1 } 158 if lsig < 0 { w("RED: no SIGNATURE label\n" as *u8); return 1 } 159 if p1 < 0 { w("RED: public key hex short/absent\n" as *u8); return 1 } 160 if p2 < 0 { w("RED: signature hex short/absent\n" as *u8); return 1 } 161 162 let msg: *u8 = sys_mmap(16) 163 var pass: i64 = 0 164 var fail: i64 = 0 165 166 if ed25519_verify_full(pub, msg, 0, sig) == 1 { 167 pass = pass + 1 168 w(" PASS T1 verify: the IETF's own signature over the empty message verifies under\n" as *u8) 169 w(" the IETF's own public key, using our ed25519 verifier\n" as *u8) 170 } else { 171 fail = fail + 1 172 w(" FAIL T1 verify: published signature REJECTED by our verifier\n" as *u8) 173 } 174 175 // ---- NEGATIVE CONTROL: the clause must be able to fail. Flip one byte of the published signature. ---- 176 // Without this the gate has not been shown to measure anything -- a verifier stuck at "accept" would 177 // pass T1 forever. 178 sig[0] = (((sig[0] as i64) ^ 1) & 255) as u8 179 if ed25519_verify_full(pub, msg, 0, sig) == 1 { 180 fail = fail + 1 181 w(" FAIL NEG: a TAMPERED signature was ACCEPTED -- the verifier is not discriminating\n" as *u8) 182 } else { 183 pass = pass + 1 184 w(" PASS NEG: one-bit-tampered signature REJECTED (the check can fail, so T1 means something)\n" as *u8) 185 } 186 187 w("\n refsrc=https://www.rfc-editor.org/rfc/rfc8032.txt\n" as *u8) 188 w(" refsrcdig=" as *u8); wb(hx, 64); w("\n" as *u8) 189 w(" ref=RFC8032-7.1 gate=nx_ed25519_extvec_gate\n" as *u8) 190 w(" SCOPE: proves our VERIFIER accepts the published signature; does NOT assert our signer.\n" as *u8) 191 w("nx_ed25519_extvec_gate: pass=" as *u8); nn(pass); w(" fail=" as *u8); nn(fail) 192 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 193 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 194 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 195 let ctr__dry: *i64 = gv_ctr() 196 ctr__dry[0] = pass 197 ctr__dry[1] = pass + fail 198 let rc__dry: i64 = gv_verdict("ED25519-EXTVEC-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 199 sys_exit(rc__dry) 200 return rc__dry 201}