The Last Yard: Foundational End-to-End Verification of High-Speed Cryptography

The field of high-assurance cryptography is quickly maturing, yet a unified foundational framework for end-to-end formal verification of efficient cryptographic implementations is still missing. To address this gap, we use the Coq proof assistant to formally connect three existing tools: (1) the Hacspec emergent cryptographic specification language; (2) the Jasmin language for efficient, high-assurance cryptographic implementations; and (3) the SSProve foundational verification framework for modular cryptographic proofs. We first connect Hacspec with SSProve by devising a new translation from Hacspec specifications to imperative SSProve code. We validate this translation by considering a second, more standard translation from Hacspec to purely functional Coq code and generate a proof of the equivalence between the code produced by the two translations. We further define a translation from Jasmin to SSProve, which allows us to formally reason in SSProve about efficient cryptographic implementations in Jasmin. We prove this translation correct in Coq with respect to Jasmin's operational semantics. Finally, we demonstrate the usefulness of our approach by giving a foundational end-to-end Coq proof of an efficient AES implementation. For this case study, we start from an existing Jasmin implementation of AES that makes use of hardware acceleration and prove that it conforms to a specification of the AES standard written in Hacspec. We use SSProve to formalize the security of the encryption scheme based on the Jasmin implementation of AES.


Introduction
Research on high-assurance cryptography recently led to signi cant practical success, with formally veri ed cryptographic code making its way into mainstream libraries and software products [7,14,16,19,21,34,37,41,42].Since in this area missing any bugs can have a serious security impact, some additionally try to reduce the trusted computing base of their veri cation tools for cryptographic code and construct foundational proofs [5,14,21,25,29,32].Such foundational proofs rely on strong logical foundations-usually by working in a proof assistant like Coq or Isabelle/HOL-and only on standard, clearly stated assumptions.Yet despite good progress in this direction, a couple of important gaps remain for foundational end-to-end cryptographic veri cation.
First, there is a speci cation gap.Currently, cryptographic primitives and protocols are speci ed only using informal pseudo-code in the standards (e.g., in IETF RFCs).The Hacspec language [15,31] aims to improve this, by making the code of these cryptographic speci cations executable, which allows them to also serve as reference implementations that can be used as oracles for testing more e cient implementations.Hacspec is a simple subset of the Rust programming language, which aims to be understandable for both ordinary developers and cryptographers.Hacspec can be translated Figure 1.Proposed work ow for foundational end-to-end veri cation of high-speed cryptography to the typed, purely functional language of proof assistants such as Coq, EasyCrypt, or F ★ , which allows sharing cryptographic speci cations across these proof assistants.
Such translations from Hacspec to a proof assistant produce a functional speci cation that can be used for verifying cryptographic code.In such a veri cation one often starts by proving the equivalence of the functional speci cation with an imperative speci cation, which is closer to the code of an implementation to be veri ed [5].We automate this step by devising a new translation from Hacspec to imperative programs in SSProve, which is a recent foundational veri cation framework for modular cryptographic proofs in Coq [1,25].Moreover, we provide translation validation infrastructure for automatically proving the equivalence of the code produced by these two translations.
Second, there is an implementation gap.Implementing cryptography in C has pitfalls: (1) unveri ed C compilers cannot be trusted to be always correct and secure [39], and (2) the CompCert veri ed C compiler does not perform aggressive optimizations and generates code with e ciency comparable only to GCC at optimization level 1 [2,28].Moreover, even aggressively optimized C programs are sometimes not fast enough since they cannot make use of special instructions providing hardware acceleration for cryptographic primitives (e.g., Intel AES-NI [23]).So cryptographic primitives are often implemented directly in assembly, at the cost of loss of abstraction, clarity, and convenience.The Jasmin language [4] was proposed as a solution to this problem.It is a language for implementing cryptographic primitives combining structured control ow with assembly instructions, which allows one to produce e cient code for x86 and ARM.Moreover, the Jasmin compiler comes with Coq proofs that it preserves the semantics of the source code [4,5] and that it does not introduce timing side-channel attacks [6].
In the fundamental 'Last Mile' paper [5], Jasmin programs are given semantics in Coq and compiled with a compiler veri ed in Coq, but reasoning about the security and correctness of Jasmin programs is done only after an unveri ed translation to EasyCrypt.In this paper, we close this gap by providing a veri ed translation from Jasmin to SSProve.Staying in Coq not only allows us to reduce the trusted computing base, but it also facilitates reusing existing mathematical Coq libraries [3,30] to verify Jasmin implementations.
Contributions.We formally connect three existing tools, Hacspec, Jasmin, and SSProve, into a uni ed foundational Coq framework for the end-to-end veri cation of high-speed cryptography (Figure 1).This includes the following novel contributions: • We devise a new translation from Hacspec speci cations to imperative SSProve code.In contrast to the existing functional translations, it allows us to reason about the stateful behavior of Hacspec.• We provide a translation validation infrastructure, which automatically produces Coq proofs of program equivalence between the results of this imperative translation and those of a more standard functional translation.We do this by performing a compositional symbolic evaluation, relating imperative code to its mathematical model.• We connect the Jasmin language and veri ed compiler to SSProve, by providing a translation of Jasmin source code to SSProve.We overcome the challenge created by the fact that SSProve only supports global state while Jasmin programs can use local state.
• We give a mechanized proof in Coq that this translation from Jasmin to SSProve preserves Jasmin's operational semantics.
• We demonstrate the usefulness of our approach on a case study by producing a foundational end-to-end Coq proof of an e cient AES implementation.We start from an existing Jasmin implementation of AES using the Intel AES-NI instructions for hardware acceleration [23] and prove (in ∼2500 lines of Coq code) that it conforms to a Hacspec speci cation of the AES standard [20].Finally, we instantiate a PRF-based symmetric encryption scheme with the implementation of AES, and use SSProve to prove IND-CPA security of this scheme under the (standard) assumption that AES is a pseudo-random function (PRF).
Outline.We start by giving an overview of our methodology and illustrating it on a very simple one-time pad example (Section 2).We then discuss necessary background (Section 3), before diving in the two formal connections we establish: the one between Hacspec and SSProve (Section 4), the other between Jasmin and SSProve (Section 5).We nally present the AES case study (Section 6), before discussing related (Section 7) and future work (Section 8).

Foundational End-to-End Veri cation, from Speci cation to E cient Implementation
In this section, we rst give an overview of our methodology following Figure 1 and then demonstrate its workings on the very simple example of a one-time pad.At a high level, we provide a foundational framework for proving the equivalence between a speci cation in Hacspec and an e cient, low-level implementation in Jasmin, by translating both to imperative SSProve programs.Once translated, we relate the programs and prove properties about them in Coq using SSProve's probabilistic relational Hoare logic.

Work ow
The work ow is illustrated in Figure 1.Starting from an informal description, such as an o cial standard (e.g., published by NIST or IETF) for a cryptographic primitive or protocol, one uses a subset of Rust with a simple, well-de ned semantics to develop a Hacspec speci cation. 1 We then automatically translate this speci cation in two ways: • once to the purely functional language of Coq; this translation produces a functional speci cation; and • once to the imperative language of SSProve; this translation produces an SSProve speci cation.The functional translation [35] targets Coq's mathematical language, and is similar to the usual functional semantics of Hacspec in F ★ and EasyCrypt.The imperative translation serves as a stepping stone towards a Jasmin implementation, which is inherently imperative.
We then perform translation validation [33] to automatically construct an equivalence proof in Coq, which formally shows that the functional and imperative Hacspec translations produce equivalent SSProve code from a given Hacspec program.More speci cally, we prove that in a clean state, the program produced by the imperative translation will return the same value as the one produced by the functional translation.The proof is conducted in SSProve's relational Hoare logic (see Section 3.3.4).
The second part of our framework concerns e cient cryptographic implementations written in Jasmin.We implemented a translation from Jasmin to the imperative language of SSProve and proved that it preserves semantics.This proof is entirely mechanized in Coq, which is possible because both SSProve and Jasmin already have formal semantics in Coq [4][5][6]25].So from the same Jasmin implementation (1) we can produce an assembly implementation using the existing Jasmin compiler, which was proved in Coq to preserve the source language semantics [4,25]; and (2) we can obtain the Jasmin Coq AST of the Jasmin implementation, which we then translate to an SSProve implementation in a way that we proved to preserve semantics.
We are now in a position to reason about the SSProve implementation using the relational probabilistic Hoare logic of SSProve.On the one hand, we can conduct an equivalence proof between the SSProve implementation obtained from Jasmin and the SSProve speci cation obtained from Hacspec.On the other hand, we can connect the translated Hacspec speci cation with security proofs done in the SSProve framework.These proofs use the standard security games from the cryptographic literature [13,24,36,38].
Formal Guarantees.By combining the correctness theorems of the Jasmin compiler and our translation to SSProve, we get the following corollary: for any function in a Jasmin program with well-de ned semantics, there exists a corresponding compiled assembly function and translated SSProve function with the same semantics, i.e., which maps equal arguments to equal results and which modi es memory in an equivalent manner.In particular, the semantics of the SSProve and assembly functions agree and we can prove the properties of the assembly program by analyzing the corresponding SSProve program; probabilistic properties cannot however be carried to the assembly level, since the semantics there are deterministic.Note that we inherit some assumptions from the compiler proof (e.g., assuming su cient stack-space) and introduce some in the translation proof (e.g., functions cannot use while-loops), see also Section 5.

One-time Pad Example
We now illustrate this methodology using a very simple example: We construct a one-time pad (OTP) from exclusive or (XOR).This toy example should convey intuition on the methodology.A more interesting case study for the AES encryption scheme is presented in Section 6.This section is to get an idea of the work ow and the ideas, however, we will introduce the background theory in more detail in Section 3.
2.2.1 Speci cation.The Hacspec speci cation for xor takes two 64-bit words as input, puts them into mutable variables, and computes their XOR (^in Hacspec).The result is stored in a mutable variable2 , which is then returned.Here letbm stands for "let bind mutable".The type both can be projected both to pure Coq and to SSProve code (see Section 4.3), resulting in the following two functions: For achieving translation validation, the both type also carries an equivalence proof between these two functions:

Jasmin Implementation.
A Jasmin implementation of xor could look as follows.
It takes two register-allocated arguments x and y (as indicated by the reg keyword) and writes the XOR of x and y into the return register r.
2.2.3 SSProve Implementation.The next step is to translate the Jasmin code to the following SSProve function.
While this readable code is not the literal output of the translation, it is the result of some careful (but semi-automated and veri ed) unfolding and simpli cation.The produced code also takes an "identi er", id0, as input: this determines which locations on the heap it will use for its local memory.This technical detail will be explained in Section 5 and can safely be ignored for now.

2.2.4
Equivalence of Implementation and Speci cation.Now that we have both translations to SSProve, we can prove that they are equivalent in our program logic.
Theorem xor_equiv : ∀ id0 w1 w2, The precondition is a predicate over the two initial heap states and the postcondition is a predicate over the two nal heaps and values.The notion of equivalence we use here to relate the two functions only requires the return values v 0 , v 1 of the two programs to be equal, provided we run them both on the same inputs.In particular, we do not make assumptions or restrict how the two programs use the heaps h 0 , h 1 .The programs are thus allowed to use di erent locations to store their intermediate values.This theorem is proved using the rules of the relational program logic of SSProve [1].

Security
Proof for the OTP Implementation.We now prove perfect cryptographic security of the Jasmin implementation of OTP using XOR.To this end, we rst need to de ne some terminology.In SSProve a package is a nite set of procedures that might contain calls to external procedures.The set it implements is called its export interface and the set on which it depends its import interface.A game is a package with no imports and a game pair is a pair of games that export the same procedures.These can be used to model cryptographic games, e.g., a game pair might consist of a real encryption scheme and an oracle: these have the same interfaces but di erent implementations.
For OTP we de ne the game pair consisting of an implementation of OTP using the Jasmin code and an implementation which is obviously secure.The Jasmin game is the package JOTP_real exporting the single procedure: Definition JOTP id0 m : k_val ← sample uniform ( 'word n) ;; JXOR id0 m k_val.
We already have a security proof for the package OTP_real exporting the single procedure: This game is already proven to be indistinguishable under chosen plaintext attack from an implementation where the message is chosen at random.The statement and proof are in the SSProve library.This is done by proving that, when the input is disregarded and a random message is encrypted, the advantage of an attacker in distinguishing between OTP_real and a game OTP_ideal is zero.
If we can prove that JOTP_real is perfectly indistinguishable from OTP_real, then we can combine the two results using the triangle inequality for advantages of games (Lemma 1 in the SSProve paper [1]) and prove that an adversary also cannot distinguish between JOTP_real and OTP_ideal, i.e., the Jasmin implementation is IND-CPA.That is, we only need to prove the following theorem.
Here ≈ 0 means that the advantage of an adversary trying to distinguish between the two games is zero.To prove this lemma we use Theorem 1 from the SSProve paper [1], which allows us to conclude if we can prove the following code equivalence for all and some stable invariant inv: For the precise de nition of stable invariant see Section 4.2 of the SSProve paper [1].In our case, we can use the invariant heap_ignore, which asserts that both heaps are preserved during execution if the locations used by JXOR are ignored.
Combining this result with the already established security of OTP_real we get security of JOTP_real.

Theorem unconditional_secrecy_jas :
That is, for all valid adversaries A with a matching interface, and all regions of adversarial memory LA, if the adversary cannot use the same locations as JXOR then their advantage in distinguishing between JOTP_real and OTP_ideal is zero.

Background & Technical Preliminaries 3.1 Hacspec
Hacspec is a High Assurance Cryptography SPECi cation language [15,27,31] aiming to provide a common language to programmers, cryptographers and proof engineers.It proposes to make future internet standards, such as those published by IETF and NIST, machine-readable.Hacspec is a subset of Rust which makes it executable and accessible to cryptographic engineers.
The Hacspec language was carefully crafted to have a functional semantics, in which assignments are translated to let-expressions.The Hacspec tool comes with functional translations to the purely functional languages of several proof assistants, currently F ★ , Coq, and EasyCrypt.As such it is a convenient tool to share speci cations across proof assistants. 3Hacspec also comes with an operational semantics [31], but since the semantics is not formalized in the backends, the functional translation cannot be veri ed against it.Instead, this translation constitutes the authoritative semantics.This motivates our choice to relate our imperative translation via translation-validation 4 .
Currently, all Hacspec backends use a functional semantics.However, both in EasyCrypt and in Coq/SSProve, one could also choose to use a translation to an embedded imperative language.This can be seen as one of the bene ts of Hacspec, as anyone familiar with either functional or imperative coding paradigms will understand the Hacspec speci cation.We will explain how to do so in Section 4.

Jasmin
Jasmin [4] is a low-level language designed for implementing high-speed cryptography, with a veri ed compiler backend supporting the x86 and ARM architectures.The language has a formal big-step operational semantics in Coq.The Jasmin compiler is also implemented and veri ed in Coq, in the sense that it preserves the semantics of the Jasmin source [4,5] and also that it does not introduce timing side-channel attacks [6].We give a condensed overview of Jasmin, focusing on the aspects that are interesting for the sake of our discussion, and limiting the explanation to a few representative examples.For more details please see the Jasmin paper [4].

3.2.1
The Language.Jasmin is an imperative language with structured control ow in the form of loops, conditionals, and procedure calls.Jasmin has types for booleans, integers, bit-words of various sizes, and arrays.Despite these high-level features, the Jasmin compiler produces predictable assembly code, which enables e cient and secure cryptographic implementations.For instance, the programmer can use architecture-speci c assembly instructions and can specify whether procedure-local variables should be stored in registers (using the reg keyword) or on the stack (using the stack keyword).Jasmin's operational semantics was carefully crafted to hide low-level details such as the distinction between the storage types reg and stack.Our correctness theorem for the translation from Jasmin to SSProve, like Jasmin's compiler correctness theorem, is proven with respect to this operational semantics, and we can thus safely ignore such distinctions.
A Jasmin program consists of a list of non-recursive function de nitions, associating to each function name a list of variables used for arguments ( ) param , variables used for returning results ( ) res , and a command, i.e., a sequence of instructions ( ) body for the body of the function.
Instructions include assignments, operators, conditionals, for and while loops, and function calls.Expressions occurring in instructions include variable and array access, arithmetic and logical operators, as well as assembly operations such as shifts, increments, etc.  (strings).Note that looking up memory in Jasmin can fail, so we will abuse notation by denoting by [ ] = that is stored at in and that it is valid to make a read of size at in .We will do the same for writes.

Jasmin Operational
Semantics.The operational semantics of Jasmin is mostly standard.A judgment of the form ⟨ | ( , ) ⟩ ⇓ ( ′ , ′ ) means that for an initial state ( , ), execution of the command terminates in the nal state ( ′ , ′ ), and ⟨ | ( , ) ⟩ ⇓ exp means that the expression evaluates to the value under state ( , ) (expressions can only read, not modify the state).All judgments are implicitly parametrized by an ambient program (i.e., list of function de nitions), which will not be mentioned explicitly unless required.For instance, in the rule for assigning a local variable in Figure 2 we start by evaluating the expression to .We then look up the type of the variable , and perform a truncation5 of at type , yielding ′ compatible with the type of .Finally, we update the local state to [ ← ′ ], while the global state remains unchanged.
Excerpt of Jasmin operational semantics The main subtlety for translating Jasmin to SSProve arises from function calls and their treatment of local state.The execution of function calls in Jasmin is split into two rules.The perspective of the caller is captured by funcall: We evaluate the arguments and perform the call to the function according to the callee's perspective.We obtain a new global state ′ and store the resulting values in the callerlocal variables .Jasmin's type checker guarantees that the number of returned values equals the number of variables.Crucially, when switching from caller to callee, we retain the local state 0 and pass only the global state to callrun as witnessed by the use of ⇓ call relating pairs of instructions and global memories and values and global memories.
To describe the callee perspective, we write 0 for the empty local state, and , , and for the body, parameter-, and result-variables of respectively.Each argument is stored in the local variable according to the de nition of parameters of ( ) param .We then execute from state ( , ), yielding ( ′ , ′ ).We obtain the values 1 , . . ., by reading the result variables from the local state ′ and truncating as necessary.Finally, the local state ′ is discarded, and the result values and updated global state ′ are returned.

SSProve
SSProve is a Coq library for modular cryptographic proofs introduced by Abate et al. [1].We only review the concepts needed to understand the current paper.More details can be found in the extended version of the SSProve paper [25].

Code.
In this paper, we de-emphasize the probabilistic capabilities of SSProve, as they are not currently re ected in Jasmin.Thus, for our purposes, SSProve essentially embeds a stateful language inside Coq using a monad called raw_code.In raw_code A one can (1) embed any pure value x of type A using ret x, (2) read from a memory location ℓ to a variable x, and use x in a continuation k, written x ← get ℓ ;; k x, (3) write a value v to a memory location ℓ and then continue with k, written put ℓ ;; k, (4) sequentially combine u : raw_code X and k : X → raw_code A using the bind operator that we write x ← u ;; k x.It is also possible to sample from a distribution D in this monad using x ← sample D ;; k x as shown in Section 2.

Memory Model.
Memory locations consist of a natural number and a type that together serve as an index in a global shared memory.This global state is represented as a map from locations to values.We say that a state is valid for a set of (typed) locations when all locations point to values of the matching type.Note that to be able to use the type in the key of the memory, we must in fact use codes of types; since SSProve is built for probabilistic programs, these codes represent types on which one may build (discrete) distributions.In type-theoretic terms, they encode a universe of datatypes choice_type which represents a subset of mathcomp's choiceType [30, §8.3].For the purposes of our translation, we use a modi ed version of SSProve where choice_type is extended to include sums, words and lists.This allows us to encode all the types needed to represent Hacspec and Jasmin programs.Memory is simulated using a structure we call heap, essentially a map from locations to values.We would like to stress the fact that in SSProve the memory is global, in contrast to Jasmin's function local state.Thus, one must take care to generate code without overlapping locations.We address this in Section 5.
For a heap ℎ, location ℓ and value , we will write ℎ[ ] and ℎ[ℓ ← ] for heap lookup and storage (as for Jasmin state).

Packages.
Another de ning feature of SSProve is that of packages.Packages are used extensively to compose modular security games in the style of state-separating proofs [17].Since our methodology allows us to reuse existing security proofs [25], we will not get into the details of security proofs, so we only introduce packages brie y.Packages are collections of procedures that can all refer to the same set of locations and invoke certain procedures that are part of an import interface.The signature of this collection de nes the export interface of the package.Packages can thus be combined modularly to create bigger packages.For instance, a package can be linked to another that implements its import interface or they can be composed in parallel to export the union of their respective export interfaces.

3.3.4
Relational Hoare Logic.Finally, SSProve features a (probabilistic) relational Hoare logic that allows us to prove the relational properties of programs.Once again, we will focus on the stateful but deterministic fragment.In this program logic, we prove judgments of the form where 0 and 1 are two code pieces we compare and and are respectively a pre-and a postcondition relating (1) the initial heaps (for ); (2) the nal heaps and nal return values of both code pieces (for ).For deterministic code, this is equivalent to: for all initial memory states 0 and 1 such that ( 0 , 1 ) holds, running in state will yield nal state ′ and nal value such that ( 0 , ′ 0 )( 1 , ′ 1 ) holds.SSProve comes with a number of rules for this logic and provides tactics to facilitate writing proofs.Moreover, one can fall back on the semantics above to prove judgments [25].

Interoperability
For the sake of getting Hacspec, Jasmin, and SSProve to interact smoothly, we had to extend each of them in a minor way.We did not, however, make any modi cations to the core projects that would change the interpretation of any of the statements that can be found in the published literature.
Speci cally, besides the translations which constitute the core contributions of this work, we made the following additions.For SSProve, we added sum types to represent the result types of Hacspec.We also added bitwidth-indexed machine words as well as lists.For Jasmin, we added the ability to pretty-print the Coq abstract syntax tree of a parsed Jasmin program, and we added the de nition of the Intel AES-NI instructions [23] to Jasmin's x86 semantics, since the AES-NI instructions are used in the AES case study.Hacspec remained unchanged.

Hacspec & SSProve
Hacspec facilitates proving the correctness of e cient implementations with respect to a speci cation by translating it to multiple proof assistants.We further this goal by adding a translation from any valid Hacspec speci cation to SSProve.This imperative translation is accompanied by a pure translation, which adds a wrapper around the existing Coq translation to embed it into SSProve.We can thus compare the imperative and pure translations using SSProve's relational logic and automatically generate a proof stating that they return the same values.

The Functional Translation
The pure translation constitutes a minor modi cation of Hacspec's existing Coq backend [35] that we undertook to facilitate the connection to Jasmin.Coq does not provide a standard library for machine integers, so the existing backend chose the CompCert library to model machine integers [28].Jasmin uses its own word library.In the long run, we would hope for a uni ed word library in the Coq ecosystem.Meanwhile, we changed the backend to use Jasmin words.
We translate for-loops as a xed point with an accumulator of all the mutable variables changed inside the loop.Hacspec has support for early return of option or result types.We model these early returns using the option and error monad.We thus need a fold operation that respects the monadic operations to allow early returns in for-loops.

The Imperative Translation
Since we provide the rst translation from Hacspec to an imperative programming language, we need to extend the information gathered in the translation from Hacspec to the various backends.SSProve needs information about what memory locations and functions are used in a given scope.To compute this, we add static dependency analysis to the Hacspec pipeline.This is done by walking the AST for every block of code and adding a unique memory location for each mutable variable.In a second pass, we then unify the memory locations used by all the local function calls, to get the total set of memory locations a function might change.
The translation evaluates arguments passed to function calls or operators before evaluating the function or operator.This is done by binding the arguments to temporary values, which are then passed to the function.This makes it easier to prove equality to another SSProve implementation, as we can rst prove that all the arguments are equal, and then show that the functions agree on equal input.
A subtlety arises from the fact that Hacspec supports early return statements: x = e? is operationally equivalent to x = match e { Some(v) => v, None => return None } In particular, if e evaluates to None, the ambient function in which the statement x = e? occurs returns early with the result None.Since SSProve's raw_code does not support control e ects, we cannot directly represent this return.We instead embed Rust code with early returns into the option monad.To ensure that this encoding interacts well with the e ectful operations of SSProve which manipulate state, we de ne a special bind operation, combining the two monads.The Hacspec code we translate carries su cient typing information to determine whether a function may return early.

Equivalence Between the Hacspec Translations
On the one hand, it is often easier to de ne and prove properties for a functional speci cation.On the other hand, it is easier to show an e cient imperative implementation equivalent to an imperative speci cation.So, it is desirable to derive an equality between the imperative and functional translations.We automatically generate such a proof, as part of the translation from the Hacspec speci cation.To achieve this we rst de ne a record both, which has projections to a piece of code for the functional translation and for the imperative translation.It also contains the proof of equivalence for the two pieces of code.We traverse the AST building the functional translation, the imperative translation and their equivalence at the same time.This is achieved by using compositional blocks for the control structures of Hacspec.
An example of such block is the one used for let expression in Hacspec, where the functional translation is a functional let binding in Coq, while the imperative translation uses bind in SSProve.The equivalence can be proven using the bind rule in SSProve, since we have a proof of equality of the arguments and a proof of equality of the rest of the code bodies.Other blocks are loops, mutable let bindings (where a location is used, as shown in Section 2.2.1), early returns, operator calls, lifting pure values, etc.We can therefore get the full translation to the imperative and functional code, together with the equality between them, by chaining these compositional blocks.This also requires us to de ne all the library functions in Hacspec in the both type.Using this combined type, we can write elements in a style where the translation looks close to the original speci cation and can be made more readable by the notation engine of Coq.

Jasmin & SSProve 5.1 Memory
A major di erence between the Jasmin and SSProve semantics is how memory is handled: SSProve only has a global notion of memory and Jasmin supports both global and local variables.To model local variables in SSProve, we parameterize all translated code over a "base stack frame ID" which reserves an (a priori unbounded) region of SSProve's global memory for local variables.Then instantiating translated code with a concrete base stack frame ID correctly assigns new stack frame IDs to all its called functions.In particular, we prove that variables translated with di erent stack frame IDs never overlap, i.e., translation of variables is injective w.r.t.IDs.We store the Jasmin global memory in a map (from integers to bytes) at a static location called MEM.

Program Translations
We now describe our translation from Jasmin to SSProve, meaning the translation of programs, but also of types, values, expressions and commands.As a rst step, we use the Jasmin compiler to pretty-print the internal AST corresponding to a Jasmin source program to Coq syntax.Since this AST datatype was extracted from Coq in the rst place, it amounts to 'de-extracting' it back to Coq.Our translation thus translates Coq's datatype of Jasmin programs to SSProve programs (i.e., the raw_code monad).

Types and Values.
The only base types missing from SSProve's choice_type (the restricted set of types which a raw_code can return; see Section 3.3.2) were words and arrays.Following Jasmin, we use the coqword library's type of words, which is based on the mathcomp library [30].We represent arrays as maps from integers to bytes.The only minor di erence is our implementation of maps di ers from Jasmin.Using similar types makes it easy to embed Jasmin values into SSProve values (via the identity) for all except array values.We denote the function taking Jasmin values to SSProve values by translate_value.

Expressions.
For the translation of expressions (denoted translate_pexpr) we have to be careful and do the right casts and truncations, as dictated by the semantics of Jasmin: e.g., when looking up in an array, the index is always cast to an integer type.For the translation of function applications in expressions (additions, subtractions, etc.), we reused the semantics from Jasmin expressions, by transporting values back to Jasmin types, applying the operations, and then transporting back to SSProve types.Note that this transport is only non-trivial for arrays.This simpli es the proof signi cantly, only requiring us to prove that all operations are invariant under this transport.

Instructions.
The main di culty in translating instructions is translating function calls; for calls to operations we could mostly use the same solution as for expressions and for for-loops we simply iterate the translated body.To be able to call functions, we choose to let our translation keep track of previously translated functions, and only allow these to be called; this avoids cyclic calls and recursion (which are always rejected by the Jasmin compiler).Furthermore, we make sure to call these translated functions with a fresh stack frame ID to avoid collisions between local variables.
Note that we currently do not translate Jasmin while loops, as they do not have a correspondent in SSProve.This does not constitute a conceptual problem in practice, since forloops are su cient for most cryptographic routines.

5.2.4
Programs.We translate Jasmin programs, which map function names to function declarations (Section 3.2.1), to maps from function names to SSProve functions taking an ID and a list of inputs to SSProve code.

Unary Deterministic Judgments
SSProve originally supported only relational judgments of the form ⊢ { { { } } } 0 ∼ 1 { { { } } }, as presented in Section 3.3.For the sake of our correctness theorem, we want to relate a translated Jasmin term 0 to the value it evaluates to, i.e., 1 is always of the form ret .Since Jasmin's semantics is deterministic, we do not need the full power of a probabilistic judgment.We thus extend SSProve and build a new unary judgment on top of the relational logic, to deal with the special case where we relate a raw_code with a return value: Here is a precondition on the initial state of , while is a postcondition on the nal state after running .The postcondition no longer mentions a nal state or return value for the right hand side, instead the return value is part of the judgment.We de ne ⊢ { { { } } } ⇓ { { { } } } as the following judgment relating to ret : The precondition only considers the memory of the left-hand side, while the postcondition also states that both sides must produce the value .While this unary judgment is conceptually simpler than the relational logic, we have found it bene cial to reuse the existing theory instead of starting from scratch.An advantage of this is that we can easily leverage the rules of the relational program logic and the tactics provided by SSProve to prove unary judgments.Moreover, we establish a precise connection between the two logics by proving that whenever is free of sampling operations, the judgment above is equivalent to saying that running on any initial state such that will yield return value and nal state ′ such that ′ .For instance, we obtain the expected rules for values, sequential composition, and writing to the heap.
Other rules can also be derived straightforwardly from the de nition of the unary judgment as analogues of the relational rules, which are detailed by Haselwarter et al. [25].

Correctness Theorem
We prove that our translation preserves the semantics of wellde ned programs.To do this we de ne a relation between Jasmin memory states and SSProve memory states.First, we relate the global Jasmin memory to the "global memory map" stored on the heap in SSProve.We say that the global Jasmin state is related to the heap ℎ when, if one can successfully read a single byte at an address from the Jasmin memory, then one can look up the corresponding value in the "global memory map" stored at MEM on the SSProve heap: To relate the local memory of Jasmin and our encoding of local memory in SSProve, we de ne a relation between a variable map and a heap ℎ relative to a stack frame ID .We write ℎ[ ] for the lookup of the variable on the heap relative to ID .A variable map is related to the heap ℎ w.r.t.if successfully looking up a variable in implies that looking up on ℎ relative to yields the same value: Now, the relation between a Jasmin memory pair ( , ) (of global and local state) and an SSProve heap is not just the conjunction over all these relations, since we need to know that a function can make an arbitrary number of function calls, each with their own local state, and not run out of space on the heap.To state this we need some terminology: We say that a stack frame ID is fresh w.r.t. a heap ℎ when 0 ∼ ℎ holds, where 0 is the empty variable map.We assume that we have a pre x order ⪯ on stack frame IDs and say that a stack frame ID is valid w.r.t. a heap ℎ when all strict successors of are fresh w.r.t.ℎ, i.e., for all ′ ≻ , 0 ∼ ′ ℎ.Furthermore, we say that two IDs 1 and 2 are disjoint, when there is no ID which they are both a pre x of.Concretely, we require for all IDs that 1 ⪯ and 2 ⪯ do not both hold simultaneously.We assume that storing at disjoint ID locations preserves values: if 1 and For a variable map , two stack frame IDs , (main and sub-ID) and a set of IDs we say that the tuple ( , , , ) is a stack frame.We say that a stack frame ( , , , ) is valid w.r.t. a heap ℎ when the following conditions hold: (1) is valid w.r.t.ℎ, (2) ∼ ℎ, (3) ∉ , (4) for all ′ ∈ , ≺ ′ , ′ is disjoint from and ′ is valid w.r.t.ℎ, (5) for all ′ , ′′ ∈ , ′ and ′′ are disjoint.
The intuition for a valid stack frame ( , , , ) is that should be related to the main stack frame ID , and the sub stack frame ID should be a valid ID from which the current function can spawn new functions with fresh memory; is there to keep track of which IDs are currently in use and to which variable maps they relate.Note that the set is only needed for the proof of correctness, and is not actually used in the translation of a given program.
A stack is then a list of stack frames.The empty stack is denoted by 0 .A stack frame ( , , , ) is disjoint from a stack when is disjoint from all sub IDs and IDs occurring in sets of the stack frames on .A stack is valid w.r.t. a heap ℎ when either is empty or = :: ′ where ′ is a valid stack and is a valid stack frame disjoint from ′ .
Using these constructions we can nally de ne our relation on Jasmin and SSProve states.A Jasmin state pair ( , ) is related to the heap ℎ w.r.t. the stack , which we write ( , ) ∼ ℎ, when the following conditions hold: (1) is valid w.r.t.ℎ, (2) ∼ ℎ, (3) is the variable map at the top of the stack, i.e., the top of the stack is of the form ( , , , ).This relation satis es two key lemmas, which are needed to prove the correctness of our translation.
These two lemmas correspond to (1) calling a function and assigning it a fresh region of memory for local state and (2) returning from a function call to its caller, accounting for the operational semantics of Jasmin function calls according to Figure 2. Note in Lemma 1 that the sub-ID of the calling stack frame, , is updated to a fresh ID 2 , and that we initialize the callee frame with the same main and sub ID 1 , since when the frame gets pushed in a function call, the callee has not invoked any further functions yet.
Using this relation, we show how our translation of Jasmin code relates to its source.For example, if we consider the function translate_pexpr, which translates Jasmin expressions to raw_code, we get the following correctness lemma.Lemma 3. Let be a value, an expression, a Jasmin state pair and a stack.
As evaluating expressions does not have memory side e ects, the relation between Jasmin and SSProve states is preserved under expression translation.
We now prove the main theorem, which establishes the connection between function calls in Jasmin and in SSProve: The theorem states that if calling the function in the Jasmin program and global memory with arguments ì results in the new global memory ′ and returns the values ì , then we can conclude two things: 1. Calling the function at a fresh ID ( 1 ) and with the translation of the arguments ì evaluates to the translation of the return values ì .2. After calling the translated function, the global memory ′ is related to heap where we have updated the sub-ID to a fresh one (from to 2 ).This is the expected behavior: calling a function can change the global but not the local state.We have to update our sub-ID because the previous one is no longer fresh, as we might have stored local state inside the function call.

AES Example
As a larger case study of our framework, we verify the security of a Jasmin implementation of a PRF-based encryption scheme using AES and prove it equivalent to a Hacspec reference implementation.The Jasmin implementation and the general methodology for proving security are similar to the presentation in EasyCrypt [8], but we use our toolchain based on SSProve to conduct the formalization.
The work ow for proving security of our AES implementation is as follows: 1. Implement the encryption scheme in Hacspec and Jasmin.2. Translate the two implementations to SSProve code.3. Prove the two translations equivalent and prove security properties of the Jasmin translation. 6 We skip implementing the Jasmin code by reusing the implementation from the EasyCrypt and Jasmin tutorial [8], which relies on the Intel AES-NI hardware acceleration instructions [23].Our reference implementation in Hacspec is based on the NIST standard [20], and it successfully passes the corresponding public test vectors [20,23].
For the security analysis, we prove indistinguishability under chosen plaintext attack (IND-CPA) of the AES implementation of the PRF-based symmetric encryption scheme described below.Concretely, we prove that the advantage of an adversary in distinguishing the encryption of a message from the encryption of a random message is (linearly) bounded by the advantage of the same adversary in distinguishing AES from a PRF.For details on the concrete bounds, see the SSProve journal paper [25, §2.3].
As was the case in Section 2, we do not have to write a security proof of the abstract encryption scheme from scratch, since such a proof, for an abstract PRF, is already present 6 Here we deviate slightly from the intended work ow from Section 2 by doing the security proof on the implementation instead of the speci cation.The reason for this is simply that parts of security proof about the Jasmin implementation were already completed when the Hacspec speci cation was added to the project.in the SSProve library [25, §2.3].To connect this with our e cient implementation, we need to prove that an adversary cannot distinguish between the e cient implementation and the abstract implementation given in SSProve [25, §2.3] instantiated with a Coq implementation of AES.
As in loc.cit., our de nitions follow SSP methodology [17].The PRF-based encryption scheme is given by the code: Here, kgen is a key generation code that uniformly samples a key on its rst invocation and returns a xed key on subsequent calls.The enc function is given by the code: Here f is the function which we assume to be a PRF and which we will instantiate with AES.The PRF is used to generate a pad from a uniformly sampled nonce r; the ciphertext is computed as the xor of the message and the pad.For all functions f : word → word → word we denote the game consisting of the single export PRF_ENC f by PRF_real f.We reuse the SSProve proof [1, §2.3] by showing that PRF_real aes is perfectly indistinguishable from the same scheme with enc replaced by the translated Jasmin code.
The high-level structure of the security analysis of the implementation is as follows: 1. Write an intermediate imperative implementation directly in SSProve code.2. Write a functional implementation directly in Coq.

Prove the equivalence between the intermediate im-
plementation and the functional implementation.4. Prove the equivalence between the translated implementation and the intermediate implementation.5. Connect the equivalences to the existing security proof of the abstract encryption scheme.
Steps ( 1) and ( 2) can also be copied almost verbatim from the EasyCrypt development: the syntactic similarities of the EasyCrypt and SSProve codes make the translation very straightforward.For the proofs in steps (3) and (4) we can reuse some parts, e.g., the loop invariants, but in general the di erences in the programming languages and the underlying proof assistants require new proofs.

Translation
As mentioned in Section 2, we start by printing the Coq ASTs of all the involved functions during Jasmin compilation.Then we use the translation described in Section 5 to obtain SSProve code for each function used in the implementation.

Speci cation
Next, we write intermediate speci cations for the Jasmin functions.Compared to the example in Section 2, these correspond to the pure Coq XOR function.As mentioned, we take inspiration from the speci cations in the EasyCrypt and Jasmin tutorial [8].This step removes translation artefacts (e.g., compiler-generated memory locations) and allows us to focus on proving the underlying logical statements.

Equivalences for Intermediate Code
Then we prove that our intermediate implementations are equivalent to functional (stateless) Coq functions.The statements we prove are generally of the form: where is arbitrary input, is the intermediate SSProve code and is the pure Coq function.Note that we also prove that these equivalences preserve the precondition ; for the equivalences to hold we usually have to assume that is stable w.r.t.memory locations used by .
Even though is usually stateless, we have to keep the heap of the right-hand side in mind, since it might be relevant in certain contexts; otherwise we could have used the unary judgments of Section 5.3.

Equivalences for Translated Code
When reasoning about the code generated by our translation from Jasmin to SSProve, we have to prove equivalences of the following form: ). ( ′ 0 , ′ 1 ) ∧ 0 = 1 } } } where ′ is the translated Jasmin program, is an arbitrary input, is a stack frame ID, is the function name in the Jasmin program and is the intermediate code.
Once we have proven such an equivalence, we can reuse it in proofs where appears as a called function.It is therefore important that the equivalences are parametric in .We also want to preserve the precondition and again we have to assume that is stable w.r.t. the locations of and .However, there is one issue here: the locations set of is not straightforward to compute and might also be rather large.Instead we require that is stable w.r.t.all possible locations used by , i.e., locations stored using an ′ with pre x ( ⪯ ′ ).This turns out to be a su cient and reasonably manageable invariant to preserve.

Connecting AES to the PRF Security Proof
The encryption function of which we want to prove the security can be implemented in Jasmin as: We then prove it perfectly indistinguishability from a similar scheme CPRF_real which uses an intermediate, simpli ed SSProve encryption function, ENC, in place of JENC.
We establish the indistinguishability by applying Theorem 1 of the SSProve paper [1].We thus have to nd a stable invariant that is preserved by a run of each of these schemes and prove that they return equal values.We prove a slight generalization of the version that theorem.Before, the invariant was required to be stable w.r.t. the nite sets of locations used by the program.Moreover, these sets were assumed to be disjoint from the state of the adversary.We now only require the invariant to be stable w.r.t.some arbitrary sets of locations assumed to be disjoint from the state of the adversary.In particular, the sets can be in nite.
Thanks to this generalization we can apply the theorem when one of the programs is the output of our translation, since we do not have to provide the concrete set of locations used by the program, but instead we can use an in nite over-approximation.We thus obtain the following.
We prove that CPRF_real is perfectly indistinguishable from PRF_real aes using the original SSProve Theorem 1 as we have better control over which locations are used.
Combining these two theorems, we get the following: the advantage of any adversary, which uses locations disjoint from JENC and from the intermediate encryption schemes, in distinguishing between JPRF_real and PRF_ENC is 0. This we can then combine with the result from the SSProve paper [1, Section 2.3] which states that PRF_ENC is IND-CPA secure up to the advantage of an adversary against aes as a PRF.

Related Work
The use of formal veri cation for cryptography has been intensely investigated, and Barbosa et al. [7] give an overview.More narrowly, work related to SSProve can be found in the extended version of the SSProve paper [25].In this section, we survey the closest related work to ours in this space.
CertiCrypt [11] is the earliest framework for reasoning about cryptographic code in Coq, but is no longer maintained.FCF [32] is a more recent foundational Coq framework for cryptographic proofs.It was used together with VST to verify the C implementations of HMAC in OpenSSL [14] and mbedTLS [41].Our work is similar in that we prove the security and correctness of the Jasmin implementation of AES.While FCF could have been a reasonable option for us, we chose SSProve because it is under active development, uses the well-developed mathcomp [30] and mathcomp-analysis libraries [3], and supports modular proofs.
EasyCrypt [9,10] is a proof assistant and veri cation tool speci cally designed for game-based cryptographic proofs.Its good integration with automatic theorem provers (e.g., SMT solvers) is helpful for large proofs, even though it comes at a cost in terms of trusted computing base.The program logics of CertiCrypt and EasyCrypt come with native support for reasoning about function calls.This was not available in SSProve before and addressing this is one of the contributions of the present work (see Section 5.1).
In the fundamental 'Last Mile' paper [5] Jasmin programs are given semantics in Coq and the correctness of the Jasmin compiler is proved in Coq with respect to this semantics.As a realistic case study, they use EasyCrypt to prove the security and correctness of a Jasmin implementation of SHA3, relying on an unveri ed translation from Jasmin to EasyCrypt.In the present work, we bridge this gap by providing a veri ed translation from Jasmin to SSProve.
CryptHOL [12] is a foundational framework for gamebased proofs that uses the theory of relational parametricity to achieve automation in Isabelle/HOL.However, unlike FCF and EasyCrypt, CryptHOL has so far not been used for the veri cation of e cient programs, as far as we are aware.
Schwabe et al. [37] prove the correctness of the C implementation of X25519 in TweetNaCl using VST.Protzenko et al. [34] verify an impressive library of cryptographic code in F*.Fiat-Crypto [21] is a foundational tool that can generate veri ed e cient implementations of nite eld arithmetic.These works are focused on correctness though and do not consider cryptographic security.
Currently, there is no formal speci cation for the complete Rust language.The Hacspec semantics can be seen as a precise semantics for a non-controversial subset of Rust.Similar proposals, but for much larger subsets of Rust, include those of Ho and Protzenko [26] and Denis et al. [18].

Future Work
Jasminify [40] is a python tool that simpli es the process of calling Jasmin code from Rust.After compiling a program, the Rust object le is replaced with the Jasmin object le.However, Jasminify does not come with any correctness guarantees.We have shown how to prove the equivalence of a Rust (Hacspec) implementation for AES with a Jasmin program.Hacspec is expressive enough to implement high-level cryptographic protocols.For such protocols, we now have a safe way to replace its cryptographic primitives by optimized Jasmin ones, as we know that their source-level semantics agree.As future work, one could try to test this toolchain, by using Jasminify, proving equivalence between the Hacspec and Jasmin implementations and then benchmarking to see what kind of performance gains one can achieve.
In concurrent work, libcrux [27] provides a library of veri ed implementations from di erent frameworks; and combines them with a safe Rust API.For example, it starts with a Hacspec reference implementation of HMAC and HKDH, and replaces their hash functions with optimized Jasmin implementations.It was proved [5] in EasyCrypt that the SHA3 implementation indeed implements a hash-function, but a formal connection with Hacspec is still missing.It would be exciting to use our framework to formally verify some of the replacements done in libcrux.
The Jasmin language is still under active development.In the present work, we devised a veri ed translation for the published version of the language [4].It would be interesting to extend our work with language features that were added to Jasmin concurrently to our work.