Extraction to the Rocq theorem prover

In addition to the (semi-)automatic and compositional proof modes already discussed above, SAW has experimental support for exporting Cryptol and SAWCore values as terms to the Rocq proof assistant[5]. This is intended to support more manual proof efforts for properties that go beyond what SAW can support (for example, proofs requiring induction) or for connecting to preexisting formalizations in Rocq of useful algorithms (e.g. the fiat crypto library[6]).

This support consists of two related pieces. The first piece is a library of formalizations of the primitives underlying Cryptol and SAWCore and various supporting concepts that help bridge the conceptual gap between SAW and Rocq. The second piece is a term translation that maps the syntactic forms of SAWCore onto corresponding concepts in Rocq syntax, designed to dovetail with the concepts defined in the support library. SAWCore is a quite similar language to the core calculus underlying Rocq, so much of this translation is quite straightforward; however, the languages are not exactly equivalent, and there are some tricky cases that mostly arise from Cryptol code that can only be partially supported. We will note these restrictions later in the manual.

We currently support Rocq version 9.1.0.

Support Library

In order to make use of SAW’s extraction capabilities, one must first compile the support library using Rocq so that the included definitions and theorems can be referenced by the extracted code. From the top directory of the SAW source tree, the source code for this support library can be found in the saw-core-rocq/rocq subdirectory. In this subdirectory you will find a _RocqProject and a Makefile. A simple make invocation should be enough to compile all the necessary files, assuming Rocq is installed and rocq is available in the user’s PATH. HTML documentation for the support library can also be generated by make html from the same directory.

Once the library is compiled, the recommended way to import it into your subsequent development is by adding the following lines to your _RocqProject file:

-Q <SAWDIR>/saw-core-rocq/rocq/generated/CryptolToRocq CryptolToRocq
-Q <SAWDIR>/saw-core-rocq/rocq/handwritten/CryptolToRocq CryptolToRocq

Here <SAWDIR> refers to the location on your system where the SAWScript source tree is checked out. This will add the relevant library files to the CryptolToRocq namespace, where the extraction process will expect to find them.

The support library for extraction is broken into two parts: those files which are handwritten, versus those that are automatically generated. The handwritten files are generally fairly readable and are reasonable for human inspection; they define most of the interesting pipe-fitting that allows Cryptol and SAWCore definitions to connect to corresponding Rocq concepts. In particular the file SAWCoreScaffolding.v file defines most of the bindings of base types to Rocq types, and the SAWCoreVectorsAsRocqVectors.v defines the core bitvector operations. The automatically generated files are direct translations of the SAWCore source files (saw-core/prelude/Prelude.sawcore and cryptol-saw-core/saw/Cryptol.sawcore) that correspond to the standard libraries for SAWCore and Cryptol, respectively.

The autogenerated files are intended to be kept up-to-date with changes in the corresponding sawcore files, and end users should not need to generate them. Nonetheless, if they are out of sync for some reason, these files may be regenerated using the saw executable by running (cd saw-core-rocq; saw saw/generate_scaffolding.saw) from the top-level of the SAW source directory before compiling them with Rocq as described above.

You may also note some additional files and concepts in the standard library, such as CompM.v, and a variety of lemmas and definitions related to it. These definitions are related to the “heapster” system, which form a separate use-case for the SAWCore to Rocq translation. These definitions will not be used for code extracted from Cryptol.

Cryptol module extraction

There are several modes of use for the SAW to Rocq term extraction facility, but the easiest to use is whole Cryptol module extraction. This will extract all the definitions in the given Cryptol module, together with it’s transitive dependencies, into a single Rocq module which can then be compiled and pulled into subsequence developments.

Suppose we have a Cryptol source file named source.cry and we want to generate a Rocq file named output.v. We can accomplish this by running the following commands in saw (either directly from the saw command prompt, or via a script file)

enable_experimental;
write_rocq_cryptol_module "source.cry" "output.v" [] [];

In this default mode, identifiers in the Cryptol source will be directly translated into identifiers in Rocq. This may occasionally cause problems if source identifiers clash with Rocq keywords or preexisting definitions. The third argument to write_rocq_cryptol_module can be used to remap such names if necessary by giving a list of (in,out) pairs of names. The fourth argument is a list of source identifiers to skip translating, if desired. Authoritative online documentation for this command can be obtained directly from the saw executable via :help write_rocq_cryptol_module after enable_experimental.

The resulting “output.v” file will have some of the usual hallmarks of computer-generated code; it will have poor formatting and, explicit parenthesis and fully-qualified names. Thankfully, once loaded into Rocq, the Rocq pretty-printer will do a much better job of rendering these terms in a somewhat human-readable way.

Proofs involving uninterpreted functions

It is possible to write a Cryptol module that references uninterpreted functions by using the primitive keyword to declare them in your Cryptol source. Primitive Cryptol declarations will be translated into Rocq section variables; as usual in Rocq, uses of these section variables will be translated into additional parameters to the definitions from outside the section. In this way, consumers of the translated module can instantiate the declared Cryptol functions with corresponding terms in subsequent Rocq developments.

Although the Cryptol interpreter itself will not be able to compute with declared but undefined functions of this sort, they can be used both to provide specifications for functions to be verified with llvm_verify or jvm_verify and also for Rocq extraction.

For example, if I write the following Cryptol source file:

primitive f : Integer -> Integer

g : Integer -> Bool
g x = f (f x) > 0

After extraction, the generated term g will have Rocq type:

(Integer -> Integer) -> Integer -> Bool

Translation limitations and caveats

Translation from Cryptol to Rocq has a number of fundamental limitations that must be addressed. The most severe of these is that Cryptol is a fully general-recursive language, and may exhibit runtime errors directly via calls to the error primitive, or via partial operations (such as indexing a sequence out-of-bounds). The internal language of Rocq, by contrast, is a strongly-normalizing language of total functions. As such, our translation is unable to extract all Cryptol programs.

Recursive programs

The most severe of the currently limitations for our system is that the translation is unable to translate any recursive Cryptol program. Doing this would require us to attempt to find some termination argument for the recursive function sufficient to satisfy Rocq; for now, no attempt is made to do so. if you attempt to extract a recursive function, SAW will produce an error about a “malformed term” with Prelude.fix as the head symbol.

Certain limited kinds of recursion are available via the foldl Cryptol primitive operation, which is translated directly into a fold operation in Rocq. This is sufficient for many basic iterative algorithms.

Type coercions

Another limitation of the translation system is that Cryptol uses SMT solvers during its typechecking process and uses the results of solver proofs to justify some of its typing judgments. When extracting these terms to Rocq, type equality coercions must be generated. Currently, we do not have a good way to transport the reasoning done inside Cryptol’s typechecker into Rocq, so we just supply a very simple Ltac tactic to discharge these coercions (see solveUnsafeAssert in CryptolPrimitivesForSAWCoreExtra.v). This tactic is able to discover simple coercions, but for anything nontrivial it may fail. The result will be a typechecking failure when compiling the generated code in Rocq when the tactic fails. If you encounter this problem, it may be possible to enhance the solveUnsafeAssert tactic to cover your use case.

Error terms

A final caveat that is worth mentioning is that Cryptol can sometimes produce runtime errors. These can arise from explicit calls to the error primitive, or from partially defined operations (e.g., division by zero or sequence access out of bounds). Such instances are translated to occurrences of an unrealized Rocq axiom named error. In order to avoid introducing an inconsistent environment, the error axiom is restricted to apply only to inhabited types. All the types arising from Cryptol programs are inhabited, so this is no problem in principle. However, collecting and passing this information around on the Rocq side is a little tricky.

The main technical device we use here is the Inhabited type class; it simply asserts that a type has some distinguished inhabitant. We provide instances for the base types and type constructors arising from Cryptol, so the necessary instances ought to be automatically constructed when needed. However, polymorphic Cryptol functions add some complications, as type arguments must also come together with evidence that they are inhabited. The translation process takes care to add the necessary Inhabited arguments, so everything ought to work out. However, if Rocq typechecking of generated code fails with errors about Inhabited class instances, it likely represents some problem with this aspect of the translation.