code wiki / _hdl_build / nx_eqsat_membership_proof_test.nx

nx_eqsat_membership_proof_test.nx source

↩ module page · 320 lines · 19765 B

1// nx_eqsat_membership_proof_test.nx -- proves the MEMBERSHIP-AS-PROOF 2// CERTIFICATE for (mul x 8) == (shl x 3), and proves the certificate is 3// LOAD-BEARING (it FAILS when a non-sound rule is used or the endpoints are not 4// in the same e-class). FAIL LOUD known-answer; exit 0 iff every check holds. 5// 6// STRUCTURE (each step self-asserts; mismatch => sys_exit(nonzero)): 7// STEP 1 SOUND SET: run the W=8 masked exhaustive battery for the SIX 8// width-trivial unary rules + mul_pow2 in-range. Establishes the 9// proven-sound set this run (total == 1536 + 2048 == 3584, all pass). 10// BOTH witnesses (algebraic Witness A AND triangulated gsim Witness B) 11// must agree on every vector. This is what JUSTIFIES the allow-list. 12// STEP 0 LOCKSTEP: the membership organ's MP_RULE_* ids must equal the 13// engine's NX_EQSAT_RULE_* (single source of truth) so the LIVE log 14// the engine writes is never mislabeled against this allow-list. 15// STEP 2 CERTIFICATE (positive, LIVE): build an e-graph, add (mul x 8) and 16// (shl x 3), ENABLE provenance, run the REAL nx_eqsat_saturate (not a 17// gated driver), and certify STRICTLY FROM THE LOG the engine wrote at 18// its union chokepoint -- mul_pow2 fires at k=3<W, logs RULE_MUL_POW2, 19// find(mul8)==find(shl3), every logged id sound + no overflow -> 20// certified. This PROMOTES mul_pow2 into the proven-sound set, now 21// exercised through the live engine + its k<W guard. 22// STEP 3 LOAD-BEARING NEG-A (non-sound rule): the gated leg cites the BOGUS 23// rule (mul x 3)==(shl x 1) -> recorder REFUSES + certificate FAILS; 24// AND a LIVE leg -- the live engine has no bogus rule, so it never 25// unions (mul x 4) with (shl x 1) => live membership/certificate FAIL. 26// STEP 4 LOAD-BEARING NEG-B (endpoints not in same class): LIVE-certify 27// (mul x 8) against (shl x 2) -- the live engine merges mul8 with 28// (shl x 3), not (shl x 2), so membership FAILS and the certificate 29// FAILS even though the log is sound. 30// STEP 5 LOAD-BEARING NEG-C (k>=W boundary): the membership organ's 31// k<MP_W side-condition (mp_mul_pow2_k_ok==0 at k==W) must REFUSE + 32// FAIL the gated certificate; in the LIVE engine this class is 33// discharged at union time by the k<NX_EQSAT_W guard (never logged). 34// STEP 6 LOAD-BEARING NEG-D (log completeness): an UNDERSIZED prov buffer 35// (cap=0) still lets the real merge happen (membership holds) but the 36// overflow flag must POISON the certificate (FAIL CLOSED) -- a 37// truncated log can never yield a passing-but-incomplete certificate. 38// 39// Known answer (FAIL LOUD): prints "<sound_passed> <sound_total> <cert> " where 40// sound_total==3584, sound_passed==3584, cert==1 (cert is now the LIVE 41// certificate). exit 0 iff all steps hold. 42 43import "nx_eqsat_membership_proof.nx" 44 45func _emit_num(v: i64) -> i64 { 46 let b: *u8 = sys_mmap(28); var n: i64 = v; if n < 0 { n = 0 - n } 47 let t2: *u8 = sys_mmap(28); var t: i64 = 0 48 if n == 0 { t2[0] = 48; t = 1 } 49 while n > 0 { t2[t] = 48 + (n % 10); n = n / 10; t = t + 1 } 50 var i: i64 = 0; while i < t { b[i] = t2[t - 1 - i]; i = i + 1 } 51 b[t] = 32; sys_write(1, b, t + 1); return 0 52} 53func _nl() -> i64 { let z: *u8 = sys_mmap(2); z[0] = 10; sys_write(1, z, 1); return 0 } 54 55// Independent cross-check: does (mul x 2^k) == (shl x k) hold for ALL x mod 2^W, 56// evaluated on the gsim (masked) vs the doubling oracle? Returns passed vectors. 57// Used as defense-in-depth for the certified equivalence and for NEG-B failure. 58func _cross_check_pow2(g: *NxGsim, vals: *i64, cells: *NxGsimCell, 59 mul_const: i64, shl_amt: i64, oracle_k: i64, 60 t: *NxTriTally) -> i64 { 61 let hi: i64 = mp_count() 62 var x: i64 = 0 63 while x < hi { 64 let lhs: i64 = _mp_eval_op_const(g, vals, cells, NX_GATE_KIND_MUL, x, mul_const) 65 let rhs: i64 = _mp_eval_op_const(g, vals, cells, NX_GATE_KIND_SHL, x, shl_amt) 66 let oracle: i64 = _mp_oracle_double_k(x, oracle_k) 67 let legs: *i64 = sys_mmap(2 * 8) as *i64 68 legs[0] = lhs 69 legs[1] = rhs 70 let v: *NxTriVerdict = sys_mmap(64) as *NxTriVerdict 71 nx_tri_pass_strict(legs, 2, oracle, 2, v) 72 nx_tri_tally_add(t, v, x) 73 x = x + 1 74 } 75 return t.passed 76} 77 78func main() -> i64 { 79 // shared gsim scratch (generous fixed caps; the netlists are tiny) 80 let vals: *i64 = sys_mmap(64 * 8) as *i64 81 let cells: *NxGsimCell = sys_mmap(64 * 48) as *NxGsimCell 82 let g: *NxGsim = sys_mmap(64) as *NxGsim 83 84 // ======================================================================== 85 // STEP 0: rule-id LOCKSTEP -- the membership organ's MP_RULE_* must match the 86 // engine's NX_EQSAT_RULE_* exactly, or the live log the engine writes could be 87 // mislabeled against this certifier's allow-list. (Defends the mislabel trap.) 88 // ======================================================================== 89 if mp_rule_ids_lockstep() != 1 { sys_exit(70); return 70 } 90 91 // ======================================================================== 92 // STEP 1: establish the SOUND SET via the W=8 masked exhaustive battery. 93 // ======================================================================== 94 let t: *NxTriTally = sys_mmap(64) as *NxTriTally 95 nx_tri_tally_init(t) 96 var sound_total: i64 = 0 97 sound_total = sound_total + mp_battery_unary(g, vals, cells, MP_RULE_ADD_ZERO, t) 98 sound_total = sound_total + mp_battery_unary(g, vals, cells, MP_RULE_SUB_SELF, t) 99 sound_total = sound_total + mp_battery_unary(g, vals, cells, MP_RULE_ADD_SELF, t) 100 sound_total = sound_total + mp_battery_unary(g, vals, cells, MP_RULE_AND_SELF, t) 101 sound_total = sound_total + mp_battery_unary(g, vals, cells, MP_RULE_OR_ZERO, t) 102 sound_total = sound_total + mp_battery_unary(g, vals, cells, MP_RULE_MUL_ONE, t) 103 let pow2_total: i64 = mp_battery_mul_pow2_inrange(g, vals, cells, t) 104 sound_total = sound_total + pow2_total 105 106 let sound_passed: i64 = t.passed 107 // six unary rules each sweep 2^W = 256 vectors -> 1536; mul_pow2 = 256*8 = 2048 108 let expect_total: i64 = 6 * mp_count() + mp_count() * MP_W 109 110 // ======================================================================== 111 // STEP 2: the MEMBERSHIP-AS-PROOF CERTIFICATE (positive) for mul x 8 == shl x 3. 112 // ======================================================================== 113 let cap_nodes: i64 = 64 114 let cap_cls: i64 = 64 115 let nodes: *NxENode = sys_mmap(cap_nodes * 64) as *NxENode 116 let classes: *NxEClass = sys_mmap(cap_cls * 32) as *NxEClass 117 let eg: *NxEGraph = sys_mmap(96) as *NxEGraph 118 if nx_eqsat_init(eg, nodes, cap_nodes, classes, cap_cls) != NX_EQSAT_OK { sys_exit(30); return 30 } 119 120 let vx: i64 = nx_eqsat_add_var(eg, 0) // x 121 let c8: i64 = nx_eqsat_add_const(eg, 8) // 8 = 2^3 122 let c3: i64 = nx_eqsat_add_const(eg, 3) // 3 123 let mul8: i64 = nx_eqsat_add_binary(eg, NX_EQ_OP_MUL, vx, c8) // (mul x 8) 124 let shl3: i64 = nx_eqsat_add_binary(eg, NX_EQ_OP_SHL, vx, c3) // (shl x 3) 125 126 // Pre-merge: distinct e-classes (membership must NOT yet hold). 127 if nx_eqsat_find(eg, mul8) == nx_eqsat_find(eg, shl3) { sys_exit(31); return 31 } 128 129 // LIVE saturation: enable provenance, run the REAL engine (NOT a gated driver), 130 // and certify STRICTLY FROM THE LOG the engine wrote at its union chokepoint. 131 // mul_pow2 fires at k=3<W (its k<W guard admits it) and merges mul8 with shl3, 132 // logging RULE_MUL_POW2 -- the ONLY real merge on this graph. This PROMOTES 133 // mul_pow2 into the proven-sound set, exercised through the live engine. 134 let plog: *i64 = sys_mmap(64 * 8) as *i64 135 if nx_eqsat_enable_prov(eg, plog, 64) != NX_EQSAT_OK { sys_exit(32); return 32 } 136 let sat2: i64 = nx_eqsat_saturate(eg, 16) 137 if sat2 != NX_EQSAT_SATURATED { sys_exit(38); return 38 } // engine converged 138 139 let cert: *NxMpCertificate = sys_mmap(64) as *NxMpCertificate 140 let cok: i64 = mp_certify_live(eg, mul8, shl3, cert) 141 if cert.member != 1 { sys_exit(33); return 33 } // MEMBERSHIP holds (live find==find) 142 if cert.sound != 1 { sys_exit(34); return 34 } // PROVENANCE: all logged ids sound 143 if cok != 1 { sys_exit(35); return 35 } // CERTIFIED (live) 144 // The live engine logged at least the mul_pow2 merge, all sound, no overflow. 145 if eg.n_prov < 1 { sys_exit(39); return 39 } // a real merge WAS logged 146 if eg.prov_overflow != 0 { sys_exit(47); return 47 } // log did not overflow 147 148 // Cross-check the SAME equivalence with a fresh triangulation battery. 149 let tc: *NxTriTally = sys_mmap(64) as *NxTriTally 150 nx_tri_tally_init(tc) 151 let cc_passed: i64 = _cross_check_pow2(g, vals, cells, 8, 3, 3, tc) 152 if tc.total != mp_count() { sys_exit(36); return 36 } 153 if cc_passed != tc.total { sys_exit(37); return 37 } // defense in depth agrees 154 155 // ======================================================================== 156 // STEP 3: LOAD-BEARING NEG-A -- a NON-sound rule must NOT certify. 157 // ======================================================================== 158 // 3a: the bogus identity's own W=8 battery must DISAGREE (so it can never 159 // enter the sound set). (mul x 3) vs (shl x 1): false for almost all x. 160 let tb: *NxTriTally = sys_mmap(64) as *NxTriTally 161 nx_tri_tally_init(tb) 162 // oracle = true (x*3) mod 2^W via doublings is NOT a power-of-two; build it 163 // directly so the oracle is the honest value, and BOTH legs (mul x 3, shl x 1) 164 // are checked against it -- they will diverge. 165 let hi: i64 = mp_count() 166 var bx: i64 = 0 167 while bx < hi { 168 let blhs: i64 = _mp_eval_op_const(g, vals, cells, NX_GATE_KIND_MUL, bx, 3) 169 let brhs: i64 = _mp_eval_op_const(g, vals, cells, NX_GATE_KIND_SHL, bx, 1) 170 let boracle: i64 = ((bx & mp_mask()) * 3) & mp_mask() // honest (x*3) mod 2^W 171 let blegs: *i64 = sys_mmap(2 * 8) as *i64 172 blegs[0] = blhs 173 blegs[1] = brhs 174 let bv: *NxTriVerdict = sys_mmap(64) as *NxTriVerdict 175 nx_tri_pass_strict(blegs, 2, boracle, 2, bv) 176 nx_tri_tally_add(tb, bv, bx) 177 bx = bx + 1 178 } 179 // The bogus identity MUST fail the battery (not all vectors agree). 180 if tb.passed == tb.total { sys_exit(40); return 40 } // bogus must NOT be sound 181 // The bogus rule id must NOT be in the proven-sound allow-list. 182 if mp_is_sound_rule(MP_RULE_BOGUS_MUL3) != 0 { sys_exit(41); return 41 } 183 184 // 3b: a gated saturation citing the bogus rule must REFUSE the merge and the 185 // certificate must FAIL. Use a fresh e-graph with (mul x 4) and (shl x 1). 186 let nodes2: *NxENode = sys_mmap(cap_nodes * 64) as *NxENode 187 let classes2: *NxEClass = sys_mmap(cap_cls * 32) as *NxEClass 188 let eg2: *NxEGraph = sys_mmap(96) as *NxEGraph 189 if nx_eqsat_init(eg2, nodes2, cap_nodes, classes2, cap_cls) != NX_EQSAT_OK { sys_exit(42); return 42 } 190 let vx2: i64 = nx_eqsat_add_var(eg2, 0) 191 let c4b: i64 = nx_eqsat_add_const(eg2, 4) 192 let c1b: i64 = nx_eqsat_add_const(eg2, 1) 193 let mul4: i64 = nx_eqsat_add_binary(eg2, NX_EQ_OP_MUL, vx2, c4b) 194 let shl1: i64 = nx_eqsat_add_binary(eg2, NX_EQ_OP_SHL, vx2, c1b) 195 let prov2: *NxMpProvenance = sys_mmap(64) as *NxMpProvenance 196 mp_prov_init(prov2) 197 // Cite the BOGUS rule -- the recorder must refuse + poison the certificate. 198 let admitted2: i64 = mp_prov_admit_union(prov2, eg2, mul4, shl1, MP_RULE_BOGUS_MUL3, 1) 199 if admitted2 != 0 { sys_exit(43); return 43 } // bogus union must be REFUSED 200 if prov2.all_sound != 0 { sys_exit(44); return 44 } // provenance poisoned 201 if prov2.bad_rule != MP_RULE_BOGUS_MUL3 { sys_exit(45); return 45 } 202 let cert2: *NxMpCertificate = sys_mmap(64) as *NxMpCertificate 203 let cok2: i64 = mp_certify(eg2, mul4, shl1, prov2, cert2) 204 if cok2 != 0 { sys_exit(46); return 46 } // certificate MUST fail (gated leg) 205 206 // 3c: LIVE leg (belt-and-suspenders) -- the live engine has NO bogus rule, so 207 // it can NEVER union (mul x 4) with (shl x 1) (the bogus claim is false; the 208 // sound mul_pow2 would merge mul4 with SHL(x,2), not shl1). So live membership 209 // FAILS => the live certificate FAILS, with a clean (all-sound) log. 210 let plog2: *i64 = sys_mmap(64 * 8) as *i64 211 if nx_eqsat_enable_prov(eg2, plog2, 64) != NX_EQSAT_OK { sys_exit(48); return 48 } 212 nx_eqsat_saturate(eg2, 16) 213 let cert2b: *NxMpCertificate = sys_mmap(64) as *NxMpCertificate 214 let cok2b: i64 = mp_certify_live(eg2, mul4, shl1, cert2b) 215 if cert2b.member != 0 { sys_exit(49); return 49 } // live engine never merges them 216 if cok2b != 0 { sys_exit(54); return 54 } // live certificate MUST fail 217 218 // ======================================================================== 219 // STEP 4: LOAD-BEARING NEG-B -- endpoints not in the same e-class. 220 // ======================================================================== 221 // Certify (mul x 8) against (shl x 2): genuinely inequivalent (8 != 2^2). 222 let nodes3: *NxENode = sys_mmap(cap_nodes * 64) as *NxENode 223 let classes3: *NxEClass = sys_mmap(cap_cls * 32) as *NxEClass 224 let eg3: *NxEGraph = sys_mmap(96) as *NxEGraph 225 if nx_eqsat_init(eg3, nodes3, cap_nodes, classes3, cap_cls) != NX_EQSAT_OK { sys_exit(50); return 50 } 226 let vx3: i64 = nx_eqsat_add_var(eg3, 0) 227 let c8c: i64 = nx_eqsat_add_const(eg3, 8) 228 let c2c: i64 = nx_eqsat_add_const(eg3, 2) 229 let mul8c: i64 = nx_eqsat_add_binary(eg3, NX_EQ_OP_MUL, vx3, c8c) 230 let shl2c: i64 = nx_eqsat_add_binary(eg3, NX_EQ_OP_SHL, vx3, c2c) 231 // LIVE: enable provenance + run the REAL saturator. mul_pow2 merges (mul x 8) 232 // with (shl x 3), NOT (shl x 2) -- so mul8c and shl2c stay in distinct classes. 233 // Membership FAILS => the live certificate FAILS, even though the log is sound. 234 let plog3: *i64 = sys_mmap(64 * 8) as *i64 235 if nx_eqsat_enable_prov(eg3, plog3, 64) != NX_EQSAT_OK { sys_exit(55); return 55 } 236 nx_eqsat_saturate(eg3, 16) 237 let cert3: *NxMpCertificate = sys_mmap(64) as *NxMpCertificate 238 let cok3: i64 = mp_certify_live(eg3, mul8c, shl2c, cert3) 239 if cert3.member != 0 { sys_exit(51); return 51 } // membership MUST fail (live) 240 if cok3 != 0 { sys_exit(52); return 52 } // certificate MUST fail (live) 241 // And the equivalence really is false: cross-check must DISAGREE. 242 let td: *NxTriTally = sys_mmap(64) as *NxTriTally 243 nx_tri_tally_init(td) 244 _cross_check_pow2(g, vals, cells, 8, 2, 3, td) // oracle = x*8, rhs = x<<2 245 if td.passed == td.total { sys_exit(53); return 53 } // must NOT all agree 246 247 // ======================================================================== 248 // STEP 5: LOAD-BEARING NEG-C -- mul_pow2 at k>=W boundary must be REFUSED. 249 // ======================================================================== 250 // At k==W the masked equality MUL/SHL coincides (0==0), so an equality sweep 251 // is BLIND to the bug -- but the k<W side-condition is not. The recorder must 252 // refuse the merge and the certificate must fail. 253 if mp_mul_pow2_k_ok(MP_W) != 0 { sys_exit(60); return 60 } // k==W is not ok 254 if mp_mul_pow2_k_ok(MP_W - 1) != 1 { sys_exit(61); return 61 } // k==W-1 is ok 255 let nodes4: *NxENode = sys_mmap(cap_nodes * 64) as *NxENode 256 let classes4: *NxEClass = sys_mmap(cap_cls * 32) as *NxEClass 257 let eg4: *NxEGraph = sys_mmap(96) as *NxEGraph 258 if nx_eqsat_init(eg4, nodes4, cap_nodes, classes4, cap_cls) != NX_EQSAT_OK { sys_exit(62); return 62 } 259 let vx4: i64 = nx_eqsat_add_var(eg4, 0) 260 let c256: i64 = nx_eqsat_add_const(eg4, 256) // 2^8 = 2^W 261 let cW: i64 = nx_eqsat_add_const(eg4, MP_W) // shift by W 262 let mul256: i64 = nx_eqsat_add_binary(eg4, NX_EQ_OP_MUL, vx4, c256) 263 let shlW: i64 = nx_eqsat_add_binary(eg4, NX_EQ_OP_SHL, vx4, cW) 264 let prov4: *NxMpProvenance = sys_mmap(64) as *NxMpProvenance 265 mp_prov_init(prov4) 266 // Cite mul_pow2 but at k==W -- the side-condition must REFUSE it. 267 let admitted4: i64 = mp_prov_admit_union(prov4, eg4, mul256, shlW, MP_RULE_MUL_POW2, MP_W) 268 if admitted4 != 0 { sys_exit(63); return 63 } // boundary union must be REFUSED 269 if prov4.all_sound != 0 { sys_exit(64); return 64 } 270 let cert4: *NxMpCertificate = sys_mmap(64) as *NxMpCertificate 271 let cok4: i64 = mp_certify(eg4, mul256, shlW, prov4, cert4) 272 if cok4 != 0 { sys_exit(65); return 65 } // certificate MUST fail at boundary 273 // Prove the equality sweep alone is BLIND here (masked MUL==SHL==0 at k=W): 274 // x * 256 mod 256 == 0 and x << 8 mod 256 == 0 for all x in [0,256). 275 var wx: i64 = 0 276 var blind_agree: i64 = 0 277 while wx < mp_count() { 278 let wl: i64 = _mp_eval_op_const(g, vals, cells, NX_GATE_KIND_MUL, wx, 256) 279 let wr: i64 = _mp_eval_op_const(g, vals, cells, NX_GATE_KIND_SHL, wx, MP_W) 280 if wl == wr { if wl == 0 { blind_agree = blind_agree + 1 } } 281 wx = wx + 1 282 } 283 if blind_agree != mp_count() { sys_exit(66); return 66 } // confirms equality is blind 284 285 // ======================================================================== 286 // STEP 6: LOAD-BEARING NEG-D -- log COMPLETENESS. An UNDERSIZED provenance 287 // buffer must FAIL CLOSED: the live engine still performs the real merge (so 288 // membership holds), but the truncated/overflowed log must POISON the 289 // certificate. A log that can silently drop entries would let an unsound 290 // union beyond the cap slip through invisibly; this proves it cannot. 291 // ======================================================================== 292 let nodes5: *NxENode = sys_mmap(cap_nodes * 64) as *NxENode 293 let classes5: *NxEClass = sys_mmap(cap_cls * 32) as *NxEClass 294 let eg5: *NxEGraph = sys_mmap(96) as *NxEGraph 295 if nx_eqsat_init(eg5, nodes5, cap_nodes, classes5, cap_cls) != NX_EQSAT_OK { sys_exit(67); return 67 } 296 let vx5: i64 = nx_eqsat_add_var(eg5, 0) 297 let c8e: i64 = nx_eqsat_add_const(eg5, 8) 298 let c3e: i64 = nx_eqsat_add_const(eg5, 3) 299 let mul8e: i64 = nx_eqsat_add_binary(eg5, NX_EQ_OP_MUL, vx5, c8e) 300 let shl3e: i64 = nx_eqsat_add_binary(eg5, NX_EQ_OP_SHL, vx5, c3e) 301 let plog5: *i64 = sys_mmap(8) as *i64 302 // cap_prov = 0 -- the very first real merge cannot be logged -> overflow flag. 303 if nx_eqsat_enable_prov(eg5, plog5, 0) != NX_EQSAT_OK { sys_exit(68); return 68 } 304 nx_eqsat_saturate(eg5, 16) 305 let cert5: *NxMpCertificate = sys_mmap(64) as *NxMpCertificate 306 let cok5: i64 = mp_certify_live(eg5, mul8e, shl3e, cert5) 307 if cert5.member != 1 { sys_exit(69); return 69 } // the merge DID happen (membership holds) 308 if eg5.prov_overflow != 1 { sys_exit(71); return 71 } // log overflowed (fail-closed armed) 309 if cert5.sound != 0 { sys_exit(72); return 72 } // truncated log => NOT sound 310 if cok5 != 0 { sys_exit(73); return 73 } // certificate MUST fail closed 311 312 // ======================================================================== 313 // FINAL known-answer gate (FAIL LOUD). 314 // ======================================================================== 315 _emit_num(sound_passed); _emit_num(sound_total); _emit_num(cok); _nl() 316 if sound_total != expect_total { sys_exit(1); return 1 } // 3584 vectors swept 317 if sound_passed != sound_total { sys_exit(2); return 2 } // every vector both-witness pass 318 if cok != 1 { sys_exit(3); return 3 } // positive certificate holds 319 sys_exit(0); return 0 320}