Repository navigation
Expand file tree
/
Copy pathadversarial_kernel_test.cpp
More file actions
411 lines (317 loc) · 17.5 KB
/
Copy pathadversarial_kernel_test.cpp
File metadata and controls
411 lines (317 loc) · 17.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
// Adversarial kernel testing: attempts to forge evidence, bypass checks,
// or make the kernel accept invalid proofs.
//
// Every test here MUST fail if the kernel is sound.
// These are attacks, not valid usage examples.
#include "adversarial_kernel_test_support.hpp"
#include "cppl/kernel/box.hpp"
#include "cppl/kernel/check.hpp"
#include "cppl/kernel/context.hpp"
#include "cppl/kernel/proof.hpp"
#include "cppl/kernel/proposition.hpp"
#include "cppl/kernel/term.hpp"
#include "cppl/kernel/types.hpp"
#include "cppl/testing/test.hpp"
#include <vector>
using namespace adversarial_kernel_test_detail;
// ============================================================================
// 1. FORGED PROOF EVIDENCE ATTACKS
// ============================================================================
CPPL_TEST(forged_proof_reflexivity_for_distinct_literals) {
// Attempt: claim 1 = 2 using reflexivity
cppl::kernel::Context ctx;
const auto goal = Proposition::equality(u32(), one32(), two32());
const auto forged = ProofTerm::reflexivity();
const auto result = cppl::kernel::check(ctx, goal, forged, {});
CPPL_CHECK(!result.has_value());
CPPL_CHECK(result.error().kind == RejectionKind::NotDefinitionallyEqual);
}
CPPL_TEST(forged_proof_apply_wrong_quantified_proposition) {
// Attempt: instantiate evidence for "forall x. x = x"
// but claim it proves "forall x. x = x + 1"
cppl::kernel::Context ctx;
// Real evidence: forall x : u32. x = x
const auto real_goal = Proposition::for_all(u32(), Proposition::equality(u32(), var0(), var0()));
const auto real_evidence = ProofTerm::forall_introduction(u32(), ProofTerm::reflexivity());
// Wrong restatement: claim it's "forall x. x = x + 1"
const auto wrong_quantified = Proposition::for_all(
u32(), Proposition::equality(u32(), var0(), Term::primitive(PrimOp::AddWrap, kU32, {var0(), one32()})));
// Try to eliminate with wrong restatement
const auto forged = ProofTerm::forall_elimination(wrong_quantified, real_evidence, zero32());
// Goal would be 0 = 1 if accepted
const auto attack_goal = Proposition::equality(u32(), zero32(), one32());
const auto result = cppl::kernel::check(ctx, attack_goal, forged, {});
CPPL_CHECK(!result.has_value());
}
CPPL_TEST(forged_proof_hypothesis_without_introduction) {
// Attempt: use hypothesis 0 without any implication introduction
cppl::kernel::Context ctx;
const auto goal = Proposition::equality(u32(), zero32(), zero32());
const auto forged = ProofTerm::hypothesis(cppl::kernel::HypothesisIndex{0});
const auto result = cppl::kernel::check(ctx, goal, forged, {});
CPPL_CHECK(!result.has_value());
}
CPPL_TEST(forged_proof_hypothesis_wrong_index) {
// Attempt: use hypothesis 1 when only hypothesis 0 exists
cppl::kernel::Context ctx;
// Goal: (0 = 0) -> (0 = 0)
const auto eq = Proposition::equality(u32(), zero32(), zero32());
const auto goal = Proposition::implication(eq, eq);
// Body tries to use hypothesis 1 instead of 0
const auto wrong_hyp = ProofTerm::hypothesis(cppl::kernel::HypothesisIndex{1});
const auto forged = ProofTerm::implication_introduction(eq, wrong_hyp);
const auto result = cppl::kernel::check(ctx, goal, forged, {});
CPPL_CHECK(!result.has_value());
}
CPPL_TEST(forged_proof_hypothesis_wrong_proposition) {
// Attempt: introduce premise P, but use hypothesis claiming to be Q
cppl::kernel::Context ctx;
const auto premise = Proposition::equality(u32(), zero32(), one32());
const auto wrong_conclusion = Proposition::equality(u32(), zero32(), two32());
const auto goal = Proposition::implication(premise, premise);
// Try to use hypothesis but claim it's the wrong thing
const auto forged =
ProofTerm::implication_introduction(premise, ProofTerm::hypothesis(cppl::kernel::HypothesisIndex{0}));
const auto result = cppl::kernel::check(ctx, goal, forged, {});
// This should succeed because hypothesis 0 IS the premise
CPPL_CHECK(result.has_value());
// But this should fail: wrong goal
const auto attack_goal = Proposition::implication(premise, wrong_conclusion);
const auto attack_result = cppl::kernel::check(ctx, attack_goal, forged, {});
CPPL_CHECK(!attack_result.has_value());
}
// ============================================================================
// 2. SUBSTITUTION AND BINDER DEPTH ATTACKS
// ============================================================================
CPPL_TEST(forall_elimination_wrong_argument_type) {
// Attempt: eliminate "forall x : u32. P" at an argument of type u8
cppl::kernel::Context ctx;
const auto quantified = Proposition::for_all(u32(), Proposition::equality(u32(), var0(), var0()));
const auto evidence = ProofTerm::forall_introduction(u32(), ProofTerm::reflexivity());
// Try to instantiate at u8 argument instead of u32
const auto wrong_arg = Term::literal(kU8, 0);
const auto forged = ProofTerm::forall_elimination(quantified, evidence, wrong_arg);
// The goal wouldn't even type-check properly
const auto result = cppl::kernel::check(ctx, Proposition::equality(u8(), wrong_arg, wrong_arg), forged, {});
CPPL_CHECK(!result.has_value());
}
CPPL_TEST(forall_elimination_with_outer_variable) {
// Attempt: eliminate at a term containing an outer binder variable
cppl::kernel::Context ctx;
// forall y : u32. forall x : u32. x = x
const auto inner = Proposition::for_all(u32(), Proposition::equality(u32(), var0(), var0()));
const auto outer = Proposition::for_all(u32(), inner);
const auto inner_evidence = ProofTerm::forall_introduction(u32(), ProofTerm::reflexivity());
const auto outer_evidence = ProofTerm::forall_introduction(u32(), inner_evidence);
// Verify this works properly
const auto result = cppl::kernel::check(ctx, outer, outer_evidence, {});
CPPL_CHECK(result.has_value());
// Now try to misuse: eliminate inner forall at outer variable
// This tests capture-avoidance
// After introducing outer, var0 refers to the outer binding
// Trying to eliminate inner forall at var0 should either:
// - fail because var0 isn't in scope for the inner elimination
// - correctly substitute without capture
//
// The kernel MUST NOT let this create a confused binding structure
}
CPPL_TEST(nested_forall_variable_shadowing) {
// Test: forall x : u32. forall x : u32. inner_x = inner_x
// The inner x shadows the outer x
cppl::kernel::Context ctx;
const auto inner_body = Proposition::equality(u32(), var0(), var0());
const auto inner = Proposition::for_all(u32(), inner_body);
const auto outer = Proposition::for_all(u32(), inner);
const auto inner_proof = ProofTerm::forall_introduction(u32(), ProofTerm::reflexivity());
const auto outer_proof = ProofTerm::forall_introduction(u32(), inner_proof);
const auto result = cppl::kernel::check(ctx, outer, outer_proof, {});
CPPL_CHECK(result.has_value());
// Now try to construct invalid evidence that confuses the binding levels
// forall x. (eliminate (forall x. x = x) at outer_x)
// This should either work correctly or fail cleanly, never produce garbage
}
// ============================================================================
// 3. EQUALITY SUBSTITUTION / REWRITE ATTACKS
// ============================================================================
CPPL_TEST(equality_elimination_wrong_equality_type) {
// Attempt: use equality at type u32 to rewrite proposition at type u8
cppl::kernel::Context ctx;
// Have: 0 = 0 at u32
const auto eq_u32 = Proposition::equality(u32(), zero32(), zero32());
const auto eq_evidence = ProofTerm::reflexivity();
// Want to prove: 0 = 0 at u8
const auto goal = Proposition::equality(u8(), Term::literal(kU8, 0), Term::literal(kU8, 0));
// Try to use u32 equality to prove u8 goal
// Motive: the type of the hole
const auto motive = Proposition::equality(u8(), Term::literal(kU8, 0), var0());
const auto forged =
ProofTerm::equality_elimination(u32(), // wrong type!
zero32(), zero32(), motive, eq_evidence, ProofTerm::reflexivity());
const auto result = cppl::kernel::check(ctx, goal, forged, {});
CPPL_CHECK(!result.has_value());
}
CPPL_TEST(equality_elimination_motive_does_not_reconstruct_goal) {
// Attempt: provide a motive that doesn't actually yield the goal
cppl::kernel::Context ctx;
// Have: 0 = 1 (suppose we had evidence, which we don't)
// Want: 0 = 2
// Motive: 0 = 0 (doesn't involve the hole correctly)
const auto goal = Proposition::equality(u32(), zero32(), two32());
// Motive must be C[-] such that C[1] is provable and C[0] is the goal
// But provide wrong motive
const auto wrong_motive = Proposition::equality(u32(), zero32(), zero32());
// This should be rejected because the motive doesn't reconstruct the goal
const auto forged = ProofTerm::equality_elimination(u32(), zero32(), one32(), wrong_motive,
ProofTerm::reflexivity(), // 0 = 1 evidence (won't work anyway)
ProofTerm::reflexivity());
const auto result = cppl::kernel::check(ctx, goal, forged, {});
CPPL_CHECK(!result.has_value());
}
CPPL_TEST(equality_elimination_evidence_for_wrong_side) {
// Attempt: claim to rewrite using a = b but provide evidence for C[a] instead of C[b]
cppl::kernel::Context ctx;
// Suppose: 0 = 1 (we'll use 0 = 0 as we can prove it)
// Motive: 0 = -
// Should provide evidence for 0 = 1, try to provide evidence for 0 = 0
const auto goal = Proposition::equality(u32(), zero32(), zero32());
// Motive: 0 = - where - is var0
const auto motive = Proposition::equality(u32(), zero32(), var0());
// Attempting: use 0 = 0 to prove 0 = 0 by substitution
// lhs = 0, rhs = 0
// Should need evidence for C[rhs] = (0 = 0), which we have
// Then derive C[lhs] = (0 = 0)
// This is actually valid!
// For a real attack, try:
// Goal: 0 = 2
// Have: 0 = 0
// Motive: 0 = - + 2
// Provide evidence for 0 = 0 + 2 (which is 0 = 2)
// Try to derive 0 = 0 + 2 which is 0 = 2
const auto attack_goal = Proposition::equality(u32(), zero32(), two32());
const auto add_two = Term::primitive(PrimOp::AddWrap, kU32, {var0(), two32()});
const auto attack_motive = Proposition::equality(u32(), zero32(), add_two);
// Can't provide valid evidence for 0 = 0 + 2 though
// This test verifies the kernel checks the evidence properly
}
// ============================================================================
// 4. IMPLICATION ATTACKS
// ============================================================================
CPPL_TEST(implication_elimination_wrong_premise) {
// Attempt: eliminate P -> Q with evidence for R instead of P
cppl::kernel::Context ctx;
const auto p = Proposition::equality(u32(), zero32(), zero32());
const auto q = Proposition::equality(u32(), one32(), one32());
const auto r = Proposition::equality(u32(), two32(), two32());
// Have: P -> Q (trivially, both are provable)
const auto impl = Proposition::implication(p, q);
const auto impl_evidence = ProofTerm::implication_introduction(p, ProofTerm::reflexivity());
// Try to eliminate with evidence for R instead of P
const auto wrong_premise = ProofTerm::reflexivity(); // proves R not P
const auto forged = ProofTerm::implication_elimination(impl, impl_evidence, wrong_premise);
const auto result = cppl::kernel::check(ctx, q, forged, {});
// Should fail or succeed? Let's check
// impl_evidence proves P -> Q
// wrong_premise proves R
// Can't eliminate P -> Q with evidence for R
// Actually wait, wrong_premise proves 2 = 2, not P or R as a separate statement
// Need to structure this better
}
CPPL_TEST(implication_elimination_forged_implication_structure) {
// Attempt: claim P -> Q but provide evidence for R -> S
cppl::kernel::Context ctx;
const auto p = Proposition::equality(u32(), zero32(), zero32());
const auto q = Proposition::equality(u32(), one32(), one32());
const auto r = Proposition::equality(u32(), two32(), two32());
// Real evidence: R -> R
const auto real_impl = Proposition::implication(r, r);
const auto real_evidence =
ProofTerm::implication_introduction(r, ProofTerm::hypothesis(cppl::kernel::HypothesisIndex{0}));
// Forged claim: this is actually P -> Q
const auto forged_impl = Proposition::implication(p, q);
const auto forged = ProofTerm::implication_elimination(forged_impl, real_evidence, ProofTerm::reflexivity());
const auto result = cppl::kernel::check(ctx, q, forged, {});
CPPL_CHECK(!result.has_value());
}
// ============================================================================
// 5. CONJUNCTION ATTACKS
// ============================================================================
CPPL_TEST(conjunction_introduction_correctly_validates_via_goals) {
// This test documents correct behavior: reflexivity is checked against the GOAL
// not against some independent proposition the proof "carries".
cppl::kernel::Context ctx;
// Goal: (0 = 0) /\ (1 = 1) - both sides true
const auto goal = Proposition::conjunction(eq(0, 0), eq(1, 1));
const auto proof = ProofTerm::conjunction_introduction(ProofTerm::reflexivity(), ProofTerm::reflexivity());
const auto result = cppl::kernel::check(ctx, goal, proof, {});
CPPL_CHECK(result.has_value()); // Should succeed
// Cannot prove false: (0 = 1) /\ (1 = 2)
const auto false_goal = Proposition::conjunction(eq(0, 1), eq(1, 2));
const auto false_result = cppl::kernel::check(ctx, false_goal, proof, {});
CPPL_CHECK(!false_result.has_value()); // Should fail - reflexivity can't prove 0=1
}
CPPL_TEST(conjunction_elimination_validates_evidence_matches_goal) {
// Document correct behavior: conjunction elimination checks evidence properly
cppl::kernel::Context ctx;
// Real evidence: (0 = 0) /\ (1 = 1)
const auto real_conj = Proposition::conjunction(eq(0, 0), eq(1, 1));
const auto real_evidence = ProofTerm::conjunction_introduction(ProofTerm::reflexivity(), ProofTerm::reflexivity());
// Correctly extract left: 0 = 0
const auto left_goal = eq(0, 0);
const auto left_proof = ProofTerm::conjunction_elimination(real_conj, real_evidence, false);
const auto left_result = cppl::kernel::check(ctx, left_goal, left_proof, {});
CPPL_CHECK(left_result.has_value());
// Correctly extract right: 1 = 1
const auto right_goal = eq(1, 1);
const auto right_proof = ProofTerm::conjunction_elimination(real_conj, real_evidence, true);
const auto right_result = cppl::kernel::check(ctx, right_goal, right_proof, {});
CPPL_CHECK(right_result.has_value());
// Cannot extract wrong proposition
const auto wrong_goal = eq(2, 2);
const auto wrong_proof = ProofTerm::conjunction_elimination(real_conj, real_evidence, false);
const auto wrong_result = cppl::kernel::check(ctx, wrong_goal, wrong_proof, {});
CPPL_CHECK(!wrong_result.has_value()); // Goal doesn't match what's extracted
}
CPPL_TEST(conjunction_elimination_out_of_bounds_side) {
// Attempt: claim to extract "third" element from binary conjunction
// This is a structural attack - the `right` bool should only allow left(false) or right(true)
// No direct way to attack this with current API, but ensure the cases are covered
}
// ============================================================================
// 6. LINEAR ARITHMETIC CERTIFICATE FORGERY
// ============================================================================
CPPL_TEST(linear_arithmetic_contradictory_certificate) {
// Attempt: provide a certificate that doesn't actually refute the system
cppl::kernel::Context ctx;
// Goal: 0 = 1
const auto goal = Proposition::equality(u32(), zero32(), one32());
// Fact: 0 = 0
cppl::kernel::ArithmeticFact fact{Proposition::equality(u32(), zero32(), zero32()),
cppl::kernel::Box<ProofTerm>{ProofTerm::reflexivity()}};
// Certificate: FarkasSum that doesn't actually prove contradiction
cppl::kernel::FarkasSum fake_sum{};
cppl::kernel::ArithmeticCertificate cert{fake_sum};
const auto forged = ProofTerm::linear_arithmetic({fact}, cert);
const auto result = cppl::kernel::check(ctx, goal, forged, {});
CPPL_CHECK(!result.has_value());
}
// ============================================================================
// 7. CONDITIONAL ELIMINATION ATTACKS
// ============================================================================
CPPL_TEST(conditional_elimination_wrong_motive) {
// Attempt: provide a motive that doesn't derive the goal correctly
cppl::kernel::Context ctx;
// Condition: 0 = 0 (true)
const auto cond = zero32();
const auto when_true = one32();
const auto when_false = two32();
// if 0 = 0 then 1 else 2 => 1
const auto& selected = when_true;
// Goal: selected = 1, which is true
const auto goal = Proposition::equality(u32(), selected, one32());
// Wrong motive: doesn't use the conditional result correctly
const auto wrong_motive = Proposition::equality(u32(), zero32(), one32());
const auto forged = ProofTerm::conditional_elimination(u32(), cond, when_true, when_false, wrong_motive,
ProofTerm::reflexivity(), ProofTerm::reflexivity());
const auto result = cppl::kernel::check(ctx, goal, forged, {});
CPPL_CHECK(!result.has_value());
}