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}