code wiki / _hdl_build / nx_rsa2048_mod_exp_big.nx
nx_rsa2048_mod_exp_big.nx source
↩ module page · 213 lines · 9567 B
1// nx_rsa2048_mod_exp_big.nx -- F103e RUNG 4: modular exponentiation with a FULL-WIDTH exponent.
2//
3// THE GAP, found by reading the signature rather than the filename: the estate's
4// `rsa2048_mod_exp(out, s, e: i64, n)` takes the exponent as an **i64**, and its own header says so
5// plainly -- "e fits in i64 (true for all Web PKI: e is either 3, 17, 65537)". That is correct and
6// sufficient for VERIFICATION, where the public exponent is tiny. It is structurally unable to do
7// either of the two things F103e needs next:
8// * RSA SIGNING: s = m^d mod n, where the private exponent d is ~2048 bits.
9// * MILLER-RABIN: a^d mod n, where d = (n-1)/2^r is ~1024 bits -- i.e. RSA KEYGEN.
10// ★★★★★★A LIBRARY NAMED FOR AN OPERATION MAY IMPLEMENT ONLY THE DIRECTION ITS AUTHOR NEEDED.
11// "The estate has RSA" was true and useless; it has RSA-verify. The symbol table said i64 and the
12// filename did not.
13//
14// This is the same square-and-multiply as the incumbent, MSB-first, but walking the bits of a u2048
15// exponent instead of an i64. It deliberately does NOT re-implement the modular multiply -- it calls
16// the SAME `rsa2048_mul_mod` the incumbent calls, so there is exactly one modmul in the estate.
17//
18// ★THE ORACLE IS THE INCUMBENT: for every exponent that fits in an i64, this function must agree with
19// `rsa2048_mod_exp` BIT FOR BIT. The teeth check e = 3, 17, 65537 and 2^63-1 against it, then
20// exercise an exponent NO i64 can hold -- the case that motivated the organ and the one the
21// incumbent cannot answer, so it is reported as EXERCISED rather than cross-checked.
22//
23// Usage: nx_rsa2048_mod_exp_big selftest
24// Exit: 0 GREEN | 1 RED. Log -> knowledge/status/nishi_os.log, verdict= LAST.
25// license_tier: ORIGINAL
26import "nx_syscalls.nx"
27import "nx_u2048.nx"
28import "nx_u2048_mul.nx"
29import "nx_rsa2048_mod.nx"
30import "nx_rsa2048_mod_exp.nx"
31const MEB_MAGIC_65537: i64 = 65537
32const MEB_MAGIC_9223372036854775807: i64 = 9223372036854775807
33
34const MEB_OK: i64 = 1
35const MEB_S_OUT_OF_RANGE: i64 = 2
36const MEB_BITS: i64 = 2048
37
38func meb_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
39func meb_fp(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 }
40func meb_fn(fd: i64, v: i64) -> i64 {
41 let bb: *u8 = sys_mmap(28); var m: i64 = v
42 if m < 0 { m = 0 - m }
43 let t: *u8 = sys_mmap(28); var k: i64 = 0
44 if m == 0 { t[0] = 48 as u8; k = 1 }
45 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
46 var i: i64 = 0
47 while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
48 sys_write(fd, bb, k); return 0
49}
50
51// highest set bit index of a u2048 exponent, or -1 if zero. Separate `found` flag ends the scan:
52// a loop that exits by clobbering its cursor destroys the position it was searching for.
53func meb_highbit(e: *i64) -> i64 {
54 var i: i64 = MEB_BITS - 1
55 while i >= 0 {
56 if u2048_get_bit(e, i) == 1 { return i }
57 i = i - 1
58 }
59 return 0 - 1
60}
61
62// out = s^e mod n, e full-width. Mirrors rsa2048_mod_exp exactly, over u2048 bits.
63func rsa2048_mod_exp_big(out: *i64, s: *i64, e: *i64, n: *i64) -> i64 {
64 if u2048_cmp(s, n) >= 0 { return MEB_S_OUT_OF_RANGE }
65 let hb: i64 = meb_highbit(e)
66 if hb < 0 { u2048_one(out); return MEB_OK } // e == 0 -> 1
67
68 let acc: *i64 = u2048_alloc()
69 let tmp: *i64 = u2048_alloc()
70 u2048_copy(acc, s) // the high bit of e
71 var i: i64 = hb - 1
72 while i >= 0 {
73 rsa2048_mul_mod(tmp, acc, acc, n) // square
74 u2048_copy(acc, tmp)
75 if u2048_get_bit(e, i) == 1 {
76 rsa2048_mul_mod(tmp, acc, s, n) // multiply
77 u2048_copy(acc, tmp)
78 }
79 i = i - 1
80 }
81 u2048_copy(out, acc)
82 return MEB_OK
83}
84
85// ---- teeth -------------------------------------------------------------------------------------
86func meb_set_i64(x: *i64, v: i64) -> i64 {
87 let b: *u8 = sys_mmap(256)
88 var i: i64 = 0
89 while i < 256 { b[i] = 0 as u8; i = i + 1 }
90 var k: i64 = 0
91 while k < 8 { b[255 - k] = ((v >> (k * 8)) & 0xff) as u8; k = k + 1 }
92 u2048_load_be(x, b)
93 return 0
94}
95
96func meb_selftest() -> i64 {
97 var pass: i64 = 0
98 var teeth: i64 = 0
99
100 let n: *i64 = u2048_alloc()
101 let s: *i64 = u2048_alloc()
102 let e: *i64 = u2048_alloc()
103 let a: *i64 = u2048_alloc()
104 let b: *i64 = u2048_alloc()
105
106 // A modulus with the top bit set (the incumbent requires it) and a base below it.
107 let nb: *u8 = sys_mmap(256)
108 var i: i64 = 0
109 while i < 256 { nb[i] = 0 as u8; i = i + 1 }
110 nb[0] = 0xC7 as u8 // top bit set
111 i = 1
112 while i < 255 { nb[i] = ((i * 37 + 11) & 0xff) as u8; i = i + 1 }
113 nb[255] = 0x8D as u8 // odd
114 u2048_load_be(n, nb)
115
116 let sb: *u8 = sys_mmap(256)
117 i = 0
118 while i < 256 { sb[i] = 0 as u8; i = i + 1 }
119 i = 8
120 while i < 256 { sb[i] = ((i * 53 + 7) & 0xff) as u8; i = i + 1 }
121 u2048_load_be(s, sb) // s << n (high bytes zero)
122
123 // T1..T4 CROSS-CHECK AGAINST THE INCUMBENT for every exponent an i64 can hold.
124 // If the general path disagrees with the special-case path anywhere they overlap, it is wrong.
125 let evs: *i64 = sys_mmap(64) as *i64
126 evs[0] = 3; evs[1] = 17; evs[2] = MEB_MAGIC_65537; evs[3] = MEB_MAGIC_9223372036854775807
127 var t: i64 = 0
128 while t < 4 {
129 teeth = teeth + 1
130 let ev: i64 = evs[t]
131 let r1: i64 = rsa2048_mod_exp(a, s, ev, n) // incumbent, small-e path
132 meb_set_i64(e, ev)
133 let r2: i64 = rsa2048_mod_exp_big(b, s, e, n) // this organ, full-width path
134 var ok: i64 = 0
135 if r1 == 1 { if r2 == MEB_OK { if u2048_eq(a, b) == 1 { ok = 1 } } }
136 if ok == 1 { pass = pass + 1; meb_p("MEB-T" as *u8); meb_fn(1, t + 1)
137 meb_p(" agrees-with-incumbent e=" as *u8); meb_fn(1, ev); meb_p(" GREEN\n" as *u8) }
138 else { meb_p("MEB-T" as *u8); meb_fn(1, t + 1); meb_p(" RED e=" as *u8); meb_fn(1, ev)
139 meb_p(" rc1=" as *u8); meb_fn(1, r1); meb_p(" rc2=" as *u8); meb_fn(1, r2); meb_p("\n" as *u8) }
140 t = t + 1
141 }
142
143 // T5 e = 0 -> 1, on both paths (the boundary the loop must not enter)
144 teeth = teeth + 1
145 u2048_zero(e)
146 rsa2048_mod_exp_big(b, s, e, n)
147 let one: *i64 = u2048_alloc()
148 u2048_one(one)
149 if u2048_eq(b, one) == 1 { pass = pass + 1; meb_p("MEB-T5 e-zero-is-one GREEN\n" as *u8) }
150 else { meb_p("MEB-T5 RED\n" as *u8) }
151
152 // T6 e = 1 -> s
153 teeth = teeth + 1
154 meb_set_i64(e, 1)
155 rsa2048_mod_exp_big(b, s, e, n)
156 if u2048_eq(b, s) == 1 { pass = pass + 1; meb_p("MEB-T6 e-one-is-s GREEN\n" as *u8) }
157 else { meb_p("MEB-T6 RED\n" as *u8) }
158
159 // T7 THE CASE THAT MOTIVATED THE ORGAN: an exponent wider than any i64. The incumbent cannot be
160 // asked, so this is reported as EXERCISED, not cross-checked -- and it is bound to a property the
161 // incumbent CAN anchor: s^(2^64) must equal squaring s sixty-four times.
162 teeth = teeth + 1
163 u2048_zero(e)
164 let eb: *u8 = sys_mmap(256)
165 i = 0
166 while i < 256 { eb[i] = 0 as u8; i = i + 1 }
167 eb[247] = 0x01 as u8 // bit 64 set -> e = 2^64, NOT i64-representable
168 u2048_load_be(e, eb)
169 let r7: i64 = rsa2048_mod_exp_big(b, s, e, n)
170 // independent construction: square s 64 times
171 let acc: *i64 = u2048_alloc()
172 let tmp: *i64 = u2048_alloc()
173 u2048_copy(acc, s)
174 var k: i64 = 0
175 while k < 64 { rsa2048_mul_mod(tmp, acc, acc, n); u2048_copy(acc, tmp); k = k + 1 }
176 if r7 == MEB_OK { if u2048_eq(b, acc) == 1 { pass = pass + 1
177 meb_p("MEB-T7 exponent-beyond-i64 (2^64) matches 64 squarings GREEN\n" as *u8) }
178 else { meb_p("MEB-T7 RED [the wide-exponent path disagrees with repeated squaring]\n" as *u8) } }
179 else { meb_p("MEB-T7 RED rc=" as *u8); meb_fn(1, r7); meb_p("\n" as *u8) }
180
181 // T8 NEGATIVE CONTROL: a different exponent must give a different result. Without this, an organ
182 // that returned s unchanged (or a constant) would pass several teeth above.
183 teeth = teeth + 1
184 meb_set_i64(e, 3)
185 rsa2048_mod_exp_big(a, s, e, n)
186 meb_set_i64(e, 5)
187 rsa2048_mod_exp_big(b, s, e, n)
188 if u2048_eq(a, b) == 0 { pass = pass + 1; meb_p("MEB-T8 neg-control-different-e-differs GREEN\n" as *u8) }
189 else { meb_p("MEB-T8 RED [VACUOUS: exponent ignored]\n" as *u8) }
190
191 // T9 range guard: s >= n must be REFUSED by name, not silently reduced.
192 teeth = teeth + 1
193 meb_set_i64(e, 3)
194 if rsa2048_mod_exp_big(b, n, e, n) == MEB_S_OUT_OF_RANGE { pass = pass + 1
195 meb_p("MEB-T9 s-out-of-range-refused GREEN\n" as *u8) }
196 else { meb_p("MEB-T9 RED\n" as *u8) }
197
198 meb_p("MEB-SELFTEST " as *u8); meb_fn(1, pass); meb_p("/" as *u8); meb_fn(1, teeth); meb_p("\n" as *u8)
199 let lf: i64 = sys_openat_append("knowledge/status/nishi_os.log" as *u8, 0x1a4)
200 if lf >= 0 {
201 meb_fp(lf, "MODEXPBIG selftest teeth=" as *u8); meb_fn(lf, pass)
202 meb_fp(lf, "of" as *u8); meb_fn(lf, teeth)
203 meb_fp(lf, " oracle=rsa2048_mod_exp-incumbent-on-i64-overlap verdict=" as *u8)
204 if pass == teeth { meb_fp(lf, "GREEN\n" as *u8) } else { meb_fp(lf, "RED\n" as *u8) }
205 sys_close(lf)
206 }
207 if pass == teeth { sys_exit(0); return 0 }
208 sys_exit(1); return 1
209}
210
211func main(argc: i64, argv: *i64) -> i64 {
212 return meb_selftest()
213}