-
Notifications
You must be signed in to change notification settings - Fork 13
/
generate_soundness.py
2637 lines (2266 loc) · 95.3 KB
/
generate_soundness.py
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
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import dataclasses
from typing import Dict, List, Optional, Tuple, Union
from starkware.cairo.lang.compiler.ast.cairo_types import CairoType, TypeFelt
from starkware.cairo.lang.compiler.ast.expr import Expression, ExprHint
from starkware.cairo.lang.compiler.instruction import Register
from starkware.cairo.lang.compiler.scoped_name import ScopedName
from starkware.cairo.lean.verification.expr_to_lean import (
LEAN_HINT_VAR_PREFIX,
LeanDescContext,
LeanExprSimplifier,
get_ap_ref_offset,
get_fp_ref_offset,
get_reversed_add_exprs,
is_ap_ref,
rec_get_const_div_inv,
to_lean_description_aux,
to_lean_paren_description_aux,
)
from starkware.cairo.lean.verification.generate_rc import SoundnessRCSteps
from starkware.cairo.lean.verification.lean_assembly_info import LeanAssemblyInfo
from starkware.cairo.lean.verification.lean_file_names import (
LeanFileNames,
mk_lean_core_import_path,
mk_lean_dependency_soundness_import_path,
mk_lean_rel_import_path,
)
from starkware.cairo.lean.verification.lean_instruction_builder import (
LeanCodeAssertEq,
LeanCodeElement,
)
from starkware.cairo.lean.verification.lean_processed_info import (
LeanBlockInfo,
LeanFunctionInfo,
LeanProgramInfo,
)
from starkware.cairo.lean.verification.lean_raw_info import (
LeanPreprocessedAddAp,
LeanPreprocessedAssertEq,
LeanPreprocessedCodeElement,
LeanPreprocessedCompoundAssertEq,
LeanPreprocessedConst,
LeanPreprocessedFuncCall,
LeanPreprocessedIf,
LeanPreprocessedJumpToLabelInstruction,
LeanPreprocessedNop,
LeanPreprocessedReference,
LeanPreprocessedReturn,
LeanPreprocessedTailCall,
LeanPreprocessedTempVar,
LeanPreprocessedTempVarAlloc,
LeanPreprocessedWithAsserts,
)
from starkware.cairo.lean.verification.rebinding import (
inc_name_sub,
name_with_sub,
next_name_with_sub,
)
from starkware.cairo.lean.verification.scope_to_lean import get_name_in_open_scopes
from starkware.cairo.lean.verification.type_to_lean import (
create_arg_defs,
get_lean_type,
get_lean_type_cast,
)
from starkware.cairo.lean.verification.var_info import LeanMemVarInfo, LeanRefVarInfo, LeanVarInfo
LEAN_CODE_INDENT = 2
@dataclasses.dataclass
class LeanGenContext:
"""
This class stores the context information for the automatic soundness
theorem generation process.
"""
func: LeanFunctionInfo
lean_info: LeanProgramInfo
assembly_info: LeanAssemblyInfo
lean_code: List[LeanCodeElement]
simplifier: LeanExprSimplifier
lvars: Dict[int, str]
# Start of block.
start_lean_desc_num: int
start_pc_offset: int
# The following lists are inherited from the previous block and then extended
# inside the block. They are used to generate the proof for this block. This means
# that if the execution can arrive at this block from multiple previous
# blocks, the following fields should be initialized by lists which are common
# to all previous blocks. For 'rewrites', for example, we can use hin_args. Currently,
# The beginning of the 'rewrites' list is the hin_args list. This is for the input
# arguments to the function. This can be extended to apply per block.
# Currently, the proof generation process can go through the same block several times
# and re-initializes the lists below every time it goes through the block.
rc_steps: Optional[SoundnessRCSteps] = None
name_sub: Dict[str, int] = dataclasses.field(default_factory=lambda: {})
rewrites: List[str] = dataclasses.field(default_factory=lambda: [])
rewrite_types: Dict[str, Tuple[CairoType, str]] = dataclasses.field(default_factory=lambda: {})
ap_rewrites: List[str] = dataclasses.field(default_factory=lambda: [])
# Current processing position.
lean_desc_num: int = 0
lean_code_elt_num: int = 0
pc_offset: int = 0
# Context information for generating Lean expressions from Cairo expressions.
desc_ctx: LeanDescContext = dataclasses.field(init=False)
# The part of the proof constructed in the current block.
prefix: str = ""
statement: List[str] = dataclasses.field(default_factory=lambda: [])
proof_intro: List[str] = dataclasses.field(default_factory=lambda: [])
main_proof: List[str] = dataclasses.field(default_factory=lambda: [])
final_proof: List[str] = dataclasses.field(default_factory=lambda: [])
# The complete proof suffix beginning at this block and ending at a return.
# The final proof parts constructed by previous blocks: since these belong
# at the end, they have to be accumulated and added at the end of the proof.
final_proof_prefix: List[List[str]] = dataclasses.field(default_factory=lambda: [])
proof: List[str] = dataclasses.field(default_factory=lambda: [])
def __post_init__(self):
self.lean_desc_num = self.start_lean_desc_num
self.pc_offset = self.start_pc_offset
self.lean_code_elt_num = self.get_lean_code_elt_num_by_pc_offset(self.pc_offset)
self.desc_ctx = LeanDescContext(
simplifier=self.simplifier,
cairo_type=None,
struct_defs=self.func.struct_defs,
identifiers=self.lean_info.identifiers,
func_scope=self.func.func_scope,
open_namespaces=self.func.open_namespaces,
div_var_basename=self.div_var_basename,
local_vars=self.lvars,
name_sub=self.name_sub,
prefix=self.prefix,
)
self.set_instr_context()
@property
def file_scope(self) -> ScopedName:
"""
Returns the top namespace opened in the soundness file, which is the
namespace corresponding to the cairo file in which the function is
defines.
"""
return self.func.func_scope[:-1]
@property
def block_info(self) -> LeanBlockInfo:
if self.start_lean_desc_num not in self.func.block_list:
raise Exception("No block info for context.")
return self.func.block_list[self.start_lean_desc_num]
@property
def block_name_suffix(self) -> str:
return mk_block_name_suffix(self)
@property
def auto_spec_name(self) -> str:
"""
The name of the automatic specification as it should be referred to when generating
the auto soundness proof, and, therefore, relative to the open namespaces.
"""
return (
mk_lean_auto_spec_name(
self.func.name,
self.lean_info.open_namespaces,
)
+ self.block_name_suffix
)
@property
def auto_soundness_theorem_name(self) -> str:
"""
The name of the auto soundness theorem as it should be used when generating
the theorem, and, therefore, relative to the namespace wherein this is generated.
"""
return (
mk_lean_auto_soundness_name(
self.func.name,
[self.func.func_scope[:-1]],
)
+ self.block_name_suffix
)
@property
def user_soundness_theorem_name(self) -> str:
"""
The name of the user soundness theorem as it should be referred to when generating
the auto soundness proof, and, therefore, relative to the open namespaces.
"""
return mk_lean_user_soundness_name(self.func.name, self.lean_info.open_namespaces)
@property
def div_var_basename(self) -> str:
return "δ{}_".format(self.pc_offset)
@property
def is_main_theorem(self) -> bool:
return self.start_lean_desc_num == 0
@property
def at_return(self) -> bool:
return isinstance(
self.func.lean_desc[self.lean_desc_num], LeanPreprocessedReturn
) or isinstance(self.func.lean_desc[self.lean_desc_num], LeanPreprocessedTailCall)
@property
def has_independent_ap(self) -> bool:
return not self.func.has_const_ap_offset and not self.is_main_theorem
def indent(self):
self.prefix += " " * LEAN_CODE_INDENT
self.desc_ctx.prefix = self.prefix
def outdent(self):
self.prefix = self.prefix[:-LEAN_CODE_INDENT]
self.desc_ctx.prefix = self.prefix
def get_lean_code_elt_num_by_pc(self, pc):
"""
Returns the lean code pc (relative to the current function) for
the given lean instruction pc (relative to the full code).
Can be used for jmp destination addresses, for example.
"""
return self.assembly_info.pc_lookup[pc] - self.assembly_info.pc_lookup[self.func.start_pc]
def get_lean_code_elt_num_by_pc_offset(self, pc_offset):
"""
Returns the lean code pc (relative to the current function) for
the given lean instruction pc (relative to the current function).
Can be used for jmp destination addresses, for example.
"""
return (
self.assembly_info.pc_lookup[self.func.start_pc + pc_offset]
- self.assembly_info.pc_lookup[self.func.start_pc]
)
def reset_pointers(self):
self.lean_desc_num = self.start_lean_desc_num
self.pc_offset = self.start_pc_offset
self.lean_code_elt_num = self.get_lean_code_elt_num_by_pc_offset(self.pc_offset)
self.set_instr_context()
def skip_asserts(self) -> Tuple[int, int]:
"""
Returns the pc offset and lean code position after skipping all asserts
generated for the current instruction (under the assumption that the
current pc offset and lean code position are at the beginning of the code
for the current instruction).
This does not change the pointer on the context object.
"""
instr = self.func.lean_desc[self.lean_desc_num]
if isinstance(instr, LeanPreprocessedAssertEq):
return (
self.pc_offset + len(self.lean_code[self.lean_code_elt_num].code),
self.lean_code_elt_num + 1,
)
if isinstance(instr, LeanPreprocessedWithAsserts):
pc_offset = self.pc_offset
lean_code_elt_num = self.lean_code_elt_num
for _ in instr.asserts:
pc_offset += len(self.lean_code[lean_code_elt_num].code)
lean_code_elt_num += 1
return (pc_offset, lean_code_elt_num)
return (self.pc_offset, self.lean_code_elt_num)
def next_instr(self):
"""
Advance the counters as a result of executing the current instruction.
This should not be called for branching instructions (conditional
jumps) and does not change the counters when called for such an instruction.
This also does not advance the counters for a return, as a return
terminates the processing of the function.
"""
instr = self.func.lean_desc[self.lean_desc_num]
# Conditional jumps and the return are not handled here.
if (
isinstance(instr, LeanPreprocessedIf)
or (
isinstance(instr, LeanPreprocessedJumpToLabelInstruction)
and instr.condition is not None
)
or isinstance(instr, LeanPreprocessedReturn)
):
return
if isinstance(instr, LeanPreprocessedNop) or isinstance(instr, LeanPreprocessedConst):
self.lean_desc_num += 1
self.set_instr_context()
return
if isinstance(instr, LeanPreprocessedJumpToLabelInstruction) and instr.condition is None:
self.pc_offset += instr.offset
self.lean_code_elt_num = self.get_lean_code_elt_num_by_pc(instr.pc_dest)
self.lean_desc_num = self.func.label_dict[instr.label_name]
elif isinstance(instr, LeanPreprocessedAddAp):
self.pc_offset += 2
self.lean_code_elt_num += 1
self.lean_desc_num += 1
elif isinstance(instr, LeanPreprocessedAssertEq) or isinstance(
instr, LeanPreprocessedWithAsserts
):
self.pc_offset, self.lean_code_elt_num = self.skip_asserts()
self.lean_desc_num += 1
if isinstance(instr, LeanPreprocessedFuncCall) or (
isinstance(instr, LeanPreprocessedTempVar) and instr.add_ap_instr is not None
):
# In addition to the asserts that prepare the instruction, we have an additional
# instruction: the call instruction or the 'ap += c' allocating the temp var.
self.pc_offset += len(self.lean_code[self.lean_code_elt_num].code)
self.lean_code_elt_num += 1
self.set_instr_context()
def add_intro(self, line: str):
self.proof_intro.append(self.prefix + line)
def concat_intro(self, lines: List[str]):
self.proof_intro += lines
def add_main(self, line: str):
self.main_proof.append(self.prefix + line)
def concat_main(self, lines: List[str]):
self.main_proof += lines
def add_final(self, line: str):
self.final_proof.append(line)
def concat_final(self, lines: List[str]):
self.final_proof += lines
def reset_context(
self,
rc_steps: Optional[SoundnessRCSteps],
name_sub: Dict[str, int],
rewrites: List[str],
rewrite_types: Dict[str, Tuple[CairoType, str]],
ap_rewrites: List[str],
):
"""
Resets the context, so that it can be used again. Resets the state pointers
to the beginning of the block. Clears the proofs. Sets the rewrites and
rc_steps to a copy of the values provided.
"""
self.reset_pointers()
self.rc_steps = rc_steps.branch() if rc_steps is not None else None
self.name_sub = name_sub.copy()
self.rewrites = rewrites.copy()
self.rewrite_types = rewrite_types.copy()
self.ap_rewrites = ap_rewrites.copy()
self.statement = []
self.proof_intro = []
self.main_proof = []
self.final_proof = []
self.reset_desc_context()
def reset_desc_context(self):
self.desc_ctx.name_sub = self.name_sub
self.desc_ctx.prefix = self.prefix
def set_instr_context(self):
"""
Set those parts of the context which depend on the current instruction.
"""
self.set_instr_simplifier()
self.set_instr_desc_ctx()
def set_instr_simplifier(self):
"""
Sets the simplifier to simplify expressions of the current instruction.
"""
instr = self.func.lean_desc[self.lean_desc_num]
self.simplifier.set_instr(instr)
def set_instr_desc_ctx(self):
"""
Set those parts of the description context that depend on the current
instruction.
"""
instr = self.func.lean_desc[self.lean_desc_num]
self.desc_ctx.div_var_basename = self.div_var_basename
self.desc_ctx.div_var_startnum = 0
self.desc_ctx.hint_vars = (
instr.get_hint_vars() if isinstance(instr, LeanPreprocessedWithAsserts) else []
)
self.desc_ctx.prefix = self.prefix
@dataclasses.dataclass
class LeanBranchCond:
# A name indicating the type of the branch (such as "if" or "jnz", for example).
# Currently this is only used to generate comments in the Lean code.
name: str
cond_var: str
# The two expressions in the condition. If this is None, the condition
# is [ap] != 0 or [ap] = 0.
exprs: Optional[Tuple[str, str]]
# Is the condition an equal or a not-equal. In the spec, the condition
# (equal or not-equal as specified in the Cairo code) appears first and its negation appears
# second. In the assembly code, the jump is always to the not-equal branch.
is_eq: bool
# Assert rewrites which apply to the condition.
assert_rw: List[str]
@dataclasses.dataclass
class LeanSoundnessGen:
func: LeanFunctionInfo
lean_info: LeanProgramInfo
assembly_info: LeanAssemblyInfo
blocks_by_lean_desc_num: Dict[int, LeanGenContext] = dataclasses.field(
default_factory=lambda: {}
)
def new_block_ctx(
self, ctx: LeanGenContext, start_lean_desc_num: int, start_pc_offset: int
) -> LeanGenContext:
"""
Generates a new context object which inherits the function's properties from
the previous context and starts at the given position.
"""
return LeanGenContext(
# Fields that depend only on the function, not the block.
func=ctx.func,
lean_info=ctx.lean_info,
assembly_info=ctx.assembly_info,
lean_code=ctx.lean_code,
simplifier=LeanExprSimplifier(ctx.lean_info.preprocessor),
lvars=ctx.lvars,
rc_steps=ctx.rc_steps.branch() if ctx.rc_steps is not None else None,
# Start position.
start_lean_desc_num=start_lean_desc_num,
start_pc_offset=start_pc_offset,
prefix=ctx.prefix,
)
def next_ctxs(
self, ctx: LeanGenContext
) -> Tuple[Optional[LeanGenContext], Optional[LeanGenContext]]:
"""
Returns the context to use after processing the current instruction.
This may be the same context as the one for the current instruction
(in which case the pointers on the context are advanced) or it may be one
or two new contexts for the one block following the instruction or the two
blocks following a branch instruction.
If the processing reached a return, the pointers are not advanced and
two Nones are returned.
If the contexts already exist in the block table, they are returned
and otherwise they are created and added to the block table.
"""
if ctx.at_return:
return (None, None)
instr = ctx.func.lean_desc[ctx.lean_desc_num]
if isinstance(instr, LeanPreprocessedIf) or (
isinstance(instr, LeanPreprocessedJumpToLabelInstruction)
and instr.condition is not None
):
jump_instr = instr.jump_instr if isinstance(instr, LeanPreprocessedIf) else instr
assert jump_instr is not None, "If statement without a jump instruction."
# Skip the asserts to the position of the conditional jump.
pc_offset, _ = ctx.skip_asserts()
if ctx.lean_desc_num + 1 in self.blocks_by_lean_desc_num:
ctx_pos = self.blocks_by_lean_desc_num[ctx.lean_desc_num + 1]
else:
ctx_pos = self.new_block_ctx(ctx, ctx.lean_desc_num + 1, pc_offset + 2)
self.blocks_by_lean_desc_num[ctx_pos.lean_desc_num] = ctx_pos
if ctx.func.label_dict[jump_instr.label_name] in self.blocks_by_lean_desc_num:
ctx_neg = self.blocks_by_lean_desc_num[ctx.func.label_dict[jump_instr.label_name]]
else:
ctx_neg = self.new_block_ctx(
ctx, ctx.func.label_dict[jump_instr.label_name], pc_offset + jump_instr.offset
)
self.blocks_by_lean_desc_num[ctx_neg.lean_desc_num] = ctx_neg
return (ctx_pos, ctx_neg)
if isinstance(instr, LeanPreprocessedJumpToLabelInstruction) and instr.condition is None:
if self.func.label_dict[instr.label_name] in self.blocks_by_lean_desc_num:
return (self.blocks_by_lean_desc_num[self.func.label_dict[instr.label_name]], None)
ctx_jmp = self.new_block_ctx(
ctx, self.func.label_dict[instr.label_name], ctx.pc_offset + instr.offset
)
self.blocks_by_lean_desc_num[ctx_jmp.lean_desc_num] = ctx_jmp
return (ctx_jmp, None)
# Non-branching instructions: advance the pointer.
ctx.next_instr()
if ctx.lean_desc_num in self.func.labels_by_lean_desc_num:
# A labeled instruction: start new block.
if ctx.lean_desc_num in self.blocks_by_lean_desc_num:
return (self.blocks_by_lean_desc_num[ctx.lean_desc_num], None)
ctx = self.new_block_ctx(ctx, ctx.lean_desc_num, ctx.pc_offset)
self.blocks_by_lean_desc_num[ctx.lean_desc_num] = ctx
return (ctx, None)
def gen_blocks(self, ctx: Optional[LeanGenContext] = None):
"""
Generates the context blocks beginning with the given context. If no
context is given, generates the context blocks beginning at the beginning
of the function.
"""
if ctx is None:
ctx = self.get_start_ctx()
if ctx.func.has_loop:
return
if ctx.at_return:
ctx.reset_pointers()
return
ctx_pos, ctx_neg = self.next_ctxs(ctx)
if ctx_pos is not None:
self.gen_blocks(ctx_pos)
if ctx_neg is not None:
self.gen_blocks(ctx_neg)
if ctx_pos != ctx:
ctx.reset_pointers()
def get_start_ctx(self) -> LeanGenContext:
"""
Generates the context which begins at the start of the function.
"""
if 0 in self.blocks_by_lean_desc_num:
return self.blocks_by_lean_desc_num[0]
lean_start_pc = self.assembly_info.pc_lookup[self.func.start_pc]
lean_end_pc = self.assembly_info.pc_lookup[self.func.end_pc]
ctx = LeanGenContext(
func=self.func,
lean_info=self.lean_info,
assembly_info=self.assembly_info,
lean_code=self.assembly_info.lean_code[lean_start_pc:lean_end_pc],
simplifier=LeanExprSimplifier(self.lean_info.preprocessor),
lvars=self.func.local_var_names(),
start_lean_desc_num=0,
start_pc_offset=0,
name_sub={name: 0 for name in self.func.arg_names},
rc_steps=SoundnessRCSteps(rc_builtin=self.func.rc)
if self.func.rc is not None
else None,
rewrites=["add_neg_eq_sub"],
)
self.blocks_by_lean_desc_num[0] = ctx
return ctx
def gen_func_proofs(self) -> List[str]:
"""
Generates the proofs for the automatic specs of all blocks.
"""
proof = []
for join_point in sorted(self.func.join_points, reverse=True):
if join_point not in self.blocks_by_lean_desc_num:
raise Exception("No generation context for join point.")
ctx = self.blocks_by_lean_desc_num[join_point]
proof += self.gen_block_proof(ctx) + [""]
proof += self.gen_block_proof(self.get_start_ctx())
return proof
def gen_block_proof(self, ctx: LeanGenContext) -> List[str]:
"""
Generates the full proof for a single block. Such a proof is an
independent lemma.
"""
ctx.reset_context(
rc_steps=SoundnessRCSteps(rc_builtin=self.func.rc)
if self.func.rc is not None
else None,
name_sub={name: 0 for name in ctx.block_info.get_arg_names()},
rewrites=["add_neg_eq_sub"] + get_block_hin_args(ctx),
rewrite_types=get_block_hin_arg_types(ctx),
ap_rewrites=[],
)
self.mk_auto_soundness_block_statement(ctx)
ctx.indent()
mk_auto_soundness_block_proof_intro(ctx)
mk_auto_soundness_block_final_proof_intro(ctx)
proof = self.gen_proof(ctx) if not ctx.func.has_loop else ["sorry"]
return ctx.statement + ["begin"] + proof + ["end"]
def gen_proof(self, ctx: LeanGenContext) -> List[str]:
"""
Continue generating the proof at the block whose context is given.
"""
ctx_pos, ctx_neg, cond = mk_auto_soundness_theorem_block(self, ctx)
if ctx_pos is None:
# Reached a return statement. Generate the final part of the proof.
mk_auto_soundness_block_proof_finish(ctx)
return (
ctx.proof_intro + ctx.main_proof + [ctx.prefix + line for line in ctx.final_proof]
)
ctx_pos.reset_context(
ctx.rc_steps, ctx.name_sub, ctx.rewrites, ctx.rewrite_types, ctx.ap_rewrites
)
ctx_pos.prefix = ctx.prefix
ctx_pos.final_proof = ctx.final_proof.copy()
if ctx_neg is not None:
ctx_pos.indent()
if cond is not None:
mk_if_branch_intro(ctx=ctx_pos, cond=cond, is_pos=True)
pos_proof = (
mk_block_apply_proof(ctx, ctx_pos)
if ctx_pos.start_lean_desc_num in ctx.func.join_points
else self.gen_proof(ctx_pos)
)
if ctx_neg is None or ctx_neg == ctx:
return ctx.proof_intro + ctx.main_proof + pos_proof
ctx_neg.reset_context(
ctx.rc_steps, ctx.name_sub, ctx.rewrites, ctx.rewrite_types, ctx.ap_rewrites
)
ctx_neg.prefix = ctx.prefix
ctx_neg.final_proof = ctx.final_proof.copy()
ctx_neg.indent()
if cond is not None:
mk_if_branch_intro(ctx=ctx_neg, cond=cond, is_pos=False)
neg_proof = (
mk_block_apply_proof(ctx, ctx_neg)
if ctx_neg.start_lean_desc_num in ctx.func.join_points
else self.gen_proof(ctx_neg)
)
return (
ctx.proof_intro
+ ctx.main_proof
+ [ctx.prefix + "{"]
+ pos_proof
+ [ctx.prefix + "},"]
+ [ctx.prefix + "{"]
+ neg_proof
+ [ctx.prefix + "}"]
)
def gen_induction_hypothesis_arg(self) -> List[str]:
"""
Generates the induction hypothesis argument, to be used when passing the
induction hypothesis of a recursive function into a block.
This is very similar to the main theorem statement.
"""
# The induction hypothesis is based on the statement of the main theorem.
ctx = self.get_start_ctx()
lines = ["(νih : ∀ (σ : register_state F)"]
lines += [
" " + line for line in self.gen_auto_soundness_block_statement_args(ctx, True)
]
lines[-1] += ","
lines += [
" " + line for line in self.gen_auto_soundness_block_statement_conclusion(ctx, True)
]
lines[-1] += ")"
return lines
def mk_auto_soundness_block_statement(self, ctx: LeanGenContext):
ctx.statement.append(f"theorem {ctx.auto_soundness_theorem_name}")
# The arguments of the statement.
ctx.statement += self.gen_auto_soundness_block_statement_args(ctx, False)
# Conclusion.
ctx.statement.append(" -- conclusion")
ctx.statement += self.gen_auto_soundness_block_statement_conclusion(ctx, False)
def gen_auto_soundness_block_statement_args(
self, ctx: LeanGenContext, is_induction_hypothesis: bool
) -> List[str]:
"""
Generates the arguments of the statement. This can also generate a version of
the arguments used in the induction hypothesis (when the function is recursive).
"""
lines = []
# Do we need an independent ap variable?
if ctx.has_independent_ap and not is_induction_hypothesis:
lines.append(" -- An independent ap variable.")
lines.append(" (ap : F)")
# Function arguments.
arg_defs = get_soundness_func_block_arg_defs(ctx)
if arg_defs:
if not is_induction_hypothesis:
lines.append(" -- arguments")
lines.append(" " + " ".join(arg_defs))
# Code in memory.
if not is_induction_hypothesis:
lines.append(" -- code is in memory at σ.pc")
lines.append(
" (h_mem : mem_at mem {} σ.pc)".format(
mk_lean_code_def_name(ctx.func.name, ctx.lean_info.open_namespaces)
)
)
# Code of dependencies in memory.
if len(ctx.func.call_dependencies) != 0:
if not is_induction_hypothesis:
lines.append(" -- all dependencies are in memory")
for dep_name, dep_offset in get_dependencies(ctx):
d_code_name = mk_lean_code_def_name(dep_name, ctx.lean_info.open_namespaces)
d_id = ctx.lean_info.function_dict[dep_name].id
d_start_pc = ctx.func.start_pc - dep_offset
offset_str = f" - {d_start_pc}" if 0 <= d_start_pc else f" + {-d_start_pc}"
lines.append(
" (h_mem_{} : mem_at mem {} (σ.pc {}))".format(d_id, d_code_name, offset_str)
)
# Input arguments offsets.
if not is_induction_hypothesis:
lines.append(" -- input arguments on the stack")
for name, var_info in ctx.block_info.arg_info.items():
cast_sep = " " if var_info.cast else ""
lines.append(
f" (hin_{name} : {name} = "
f"{var_info.cast}{cast_sep}{mk_hin_arg_expr(ctx, var_info)})"
)
if not ctx.is_main_theorem:
lines.append(" (νbound : ℕ)")
if ctx.func.is_recursive:
lines += [" " + line for line in self.gen_induction_hypothesis_arg()]
return lines
def gen_auto_soundness_block_statement_conclusion(
self, ctx: LeanGenContext, is_induction_hypothesis: bool
) -> List[str]:
"""
Generates the conclusion of the statement. This can also generate a version of
the conclusion used in the induction hypothesis (when the function is recursive).
"""
lines = []
has_const_ap_offset = ctx.func.has_const_ap_offset
ap_offset = ctx.block_info.flow_at_start.ap_tracking.offset
args = " ".join(ctx.block_info.get_arg_names())
ret_offsets_etc = get_auto_soundness_ret_types_offsets_and_casts(
ctx.func, ctx.lean_info, " "
)
ret_vals = [f"({cast}mem (τ.ap - {-offset}))" for _, offset, cast in ret_offsets_etc]
full_args = (" " + args if args else "") + (" " + " ".join(ret_vals) if ret_vals else "")
spec_name = (
mk_lean_user_spec_name(ctx.func.name, ctx.lean_info.open_namespaces)
if ctx.is_main_theorem
else ctx.auto_spec_name
)
f_props = (
"τ.ap = σ.ap + {} ∧".format(ctx.func.get_total_ap_offset())
if has_const_ap_offset
else ""
)
if ctx.is_main_theorem:
if not is_induction_hypothesis:
lines.append(" : ensures_ret mem σ (λ κ τ,")
else:
lines.append(" ensuresb_ret νbound mem σ (λ κ τ,")
else:
lines.append(" : ensuresb_ret νbound mem")
start_ap = (
"ap"
if ctx.has_independent_ap
else "σ.ap" + (f" + {ap_offset}" if ap_offset else "")
)
lines.append(
f" {{pc := σ.pc + {ctx.start_pc_offset}, ap := {start_ap}, fp := σ.fp}}"
)
lines.append(" (λ κ τ,")
if f_props:
if ctx.func.has_range_check():
lines.append(" " + f_props.strip())
else:
lines[-1] += " " + f_props
end_op = "" if is_induction_hypothesis else " :="
if ctx.func.rc is not None:
rc_var_info = ctx.block_info.get_arg_info_by_name(ctx.func.rc.arg_name)
if (
rc_var_info is None
or not isinstance(rc_var_info, LeanMemVarInfo)
or rc_var_info.offset is None
):
raise Exception("Failed to find rc variable offset in block variables.")
_, rc_ret_offset, _ = ret_offsets_etc[ctx.func.rc.arg_index]
lines.append(
" ∃ μ ≤ κ, rc_ensures mem (rc_bound F) μ "
f"({mk_hin_arg_expr(ctx, rc_var_info)}) (mem $ τ.ap - {-rc_ret_offset})",
)
lines.append(f" ({spec_name} mem κ{full_args})){end_op}")
else:
lines[-1] += f" {spec_name} mem κ{full_args}){end_op}"
return lines
def get_dependencies(ctx: LeanGenContext) -> List[Tuple[str, int]]:
"""
Returns the list of dependencies of the function. Each dependency is
described by a tuple <name, pc offset>. The list is sorted by the pc offset.
"""
return sorted(
[
(dep_name, ctx.lean_info.function_dict[dep_name].start_pc)
for dep_name in ctx.func.call_dependencies
],
key=lambda p: p[1],
)
def get_dep_args(ctx: LeanGenContext) -> List[str]:
"""
Returns the sorted list of dependency arguments.
"""
return [
f"h_mem_{ctx.lean_info.function_dict[dep_name].id}" for dep_name, _ in get_dependencies(ctx)
]
def get_hin_args(ctx: LeanGenContext) -> List[str]:
offsets_etc = get_auto_soundness_arg_types_offsets_and_casts(ctx.func, ctx.lean_info, "")
return [f"hin_{name}" for name, _ in offsets_etc.items()]
def get_block_hin_args(ctx: LeanGenContext) -> List[str]:
return [f"hin_{name}" for name in ctx.block_info.arg_info]
def get_hin_arg_types(ctx: LeanGenContext) -> Dict[str, Tuple[CairoType, str]]:
"""
The type and cast for the input arguments which are not of type F.
"""
types = {}
offsets_etc = get_auto_soundness_arg_types_offsets_and_casts(ctx.func, ctx.lean_info, "")
for name, (arg_type, _, cast) in offsets_etc.items():
if not isinstance(arg_type, TypeFelt):
types[f"hin_{name}"] = (arg_type, cast)
return types
def get_block_hin_arg_types(ctx: LeanGenContext) -> Dict[str, Tuple[CairoType, str]]:
"""
The type and cast for the block input arguments which are not of type F.
"""
types = {}
for name, var_info in ctx.block_info.arg_info.items():
if not isinstance(var_info.cairo_type, TypeFelt):
types[f"hin_{name}"] = (var_info.cairo_type, var_info.cast)
return types
def mk_hin_arg_expr(ctx: LeanGenContext, var_info: LeanVarInfo) -> str:
"""
Returns the Lean expression defining an input variable in terms of offsets
from the register pointers (fp and ap).
"""
if isinstance(var_info, LeanMemVarInfo):
reg_str = (
"σ.fp"
if var_info.reg == Register.FP
else ("σ.ap" if ctx.func.has_const_ap_offset else "ap")
)
offset = var_info.offset
if var_info.reg == Register.AP and ctx.func.has_const_ap_offset:
offset += ctx.block_info.flow_at_start.ap_tracking.offset
return (
f"mem {reg_str}"
if offset == 0
else (f"mem ({reg_str} - {-offset})" if offset < 0 else f"mem ({reg_str} + {offset})")
)
if isinstance(var_info, LeanRefVarInfo):
return "({})".format(
var_info.lean_expr
if ctx.func.has_const_ap_offset
else var_info.lean_expr.replace("σ.ap", "ap")
)
raise Exception("Verification block: unsupported variable type.")
def mk_auto_soundness_block_proof_intro(ctx: LeanGenContext):
"""
Generates the first part of the soundness proof, which is still independent
of the instructions of the function, but depends on the inputs and on
general properties of the function (for example, whether it is recursive).
"""
if ctx.is_main_theorem:
ctx.add_intro("apply ensures_of_ensuresb, intro νbound,")
if ctx.func.is_recursive and ctx.is_main_theorem:
# Assume inductively that the theorem spec holds for shorter execution traces.
theorem_arglist = " ".join(
["σ"] + ctx.func.arg_names + ["h_mem"] + get_dep_args(ctx) + get_hin_args(ctx)
)
ctx.add_intro("revert " + theorem_arglist + ",")
ctx.add_intro("induction νbound with νbound νih,")
ctx.add_intro("{ intros, intros n nlt, apply absurd nlt (nat.not_lt_zero _) },")
ctx.add_intro("intros " + theorem_arglist + ",")
ctx.add_intro("dsimp at νih,")
ctx.add_intro("have h_mem_rec := h_mem,")
# Unpack the memory.
code_length = ctx.func.end_pc - ctx.func.start_pc
pc_hyps = map(lambda i: "hpc" + str(i), range(code_length))
ctx.add_intro(
"unpack_memory {} at h_mem with ⟨{}⟩,".format(
mk_lean_code_def_name(ctx.func.name, ctx.lean_info.open_namespaces), ", ".join(pc_hyps)
)
)
# Names for definitions and theorems.
def mk_lean_code_def_name(fn_name: str, namespaces: List[ScopedName]):
prefix = "code_"
return get_name_in_open_scopes(ScopedName.from_string(fn_name), namespaces, prefix)
def mk_lean_auto_spec_name(fn_name: str, namespaces: List[ScopedName]):
prefix = "auto_spec_"
return get_name_in_open_scopes(ScopedName.from_string(fn_name), namespaces, prefix)
def mk_lean_user_spec_name(fn_name: str, namespaces: List[ScopedName]):
prefix = "spec_"
return get_name_in_open_scopes(ScopedName.from_string(fn_name), namespaces, prefix)
def mk_lean_user_soundness_name(fn_name: str, namespaces: List[ScopedName]):
prefix = "sound_"
return get_name_in_open_scopes(ScopedName.from_string(fn_name), namespaces, prefix)
def mk_lean_auto_soundness_name(fn_name: str, namespaces: List[ScopedName]):
prefix = "auto_sound_"
return get_name_in_open_scopes(ScopedName.from_string(fn_name), namespaces, prefix)
def mk_block_name_suffix(ctx: LeanGenContext) -> str:
return f"_block{ctx.start_lean_desc_num}" if not ctx.is_main_theorem else ""
@dataclasses.dataclass
class LeanExprSimps:
"""
An auxiliary class to store rewrites which are needed as a result of transformations
applied to expressions by the compiler's simplifier.
"""
# Rewrites needed for division by a constant.
const_div_rw: List[str] = dataclasses.field(default_factory=lambda: [])
# Rewrites for x + y -> y + x (when x is a constant)
add_comm: List[str] = dataclasses.field(default_factory=lambda: [])
def is_empty(self) -> bool:
return len(self.const_div_rw) == 0 and len(self.add_comm) == 0
# The procedures that do all the real work.
# Code definition.
def mk_lean_function_code_def(func_name: str, main_scope: ScopedName, code: List[str], out):
print(
"def {} : list F := [".format(mk_lean_code_def_name(func_name, [main_scope])),