% Markierungen: TODO
\documentclass{llncs}

\usepackage{etex}
\pagestyle{plain} % turn on page numbers
\usepackage[utf8]{inputenc}
\usepackage{newunicodechar}

\usepackage{microtype} % Better typesetting for PDFs -- is enabling this ok?
\usepackage{amsmath}
\usepackage{amssymb}
%\usepackage{eufrak} %The eufrak package is redundant if the amsfonts package is used
% \usepackage{mathpartir}
%\DeclareMathAlphabet{\mathpzc}{OT1}{pzc}{m}{it}
\usepackage[boxed]{algorithm}
\usepackage{enumerate}
\usepackage{listings}
\usepackage{lstautogobble}
\usepackage{graphicx}
\usepackage{tabularx}
\usepackage{booktabs}
\usepackage{color}
\usepackage[noend]{algpseudocode}
\usepackage{caption}
\usepackage[font=scriptsize]{subcaption}
\usepackage{hyperref}
\usepackage{float}
\usepackage{wrapfig}
\usepackage{multirow}
\usepackage{pgfplots}

\usepackage{relsize}
\usepackage{cite}


\usepackage{packages/isabelle}
\usepackage{packages/isabelletags}
\usepackage{packages/isabellesym}
\usepackage{packages/comment}

% \isabellestyle{it}

\def\isachardoublequote{}%
\def\isachardoublequoteopen{}%
\def\isachardoublequoteclose{}%


\newcommand{\isainnerkeyword}[1]{{\tt #1}}
\newcommand{\isasymexistsA}{\isamath{\exists_{\textsc A}\,}}


\def\isadelimproof{}
\def\endisadelimproof{}
\def\isatagproof{}
\def\endisatagproof{}
\def\isafoldproof{}
\def\isadelimproof{}
\def\endisadelimproof{}

\input{lstisabelle}
\lstset{basicstyle=\footnotesize\ttfamily\slshape}
\lstset{captionpos=b}
\lstset{numberbychapter=false}
\lstset{autogobble=true}

\newcommand{\isai}{\lstinline[language=isabelle,basicstyle=\normalsize\ttfamily\slshape]}
\newcommand{\lsti}{\lstinline[language={},literate={}]}

\input{macros}

\newcommand\CC{C\nolinebreak[4]\hspace{-.05em}\raisebox{.4ex}{\relsize{-3}{\textbf{++}}}}

% Include snippets
\newcommand{\DefineSnippet}[2]{%
   \expandafter\newcommand\csname snippet--#1\endcsname{%
     \begin{quote}
     \begin{isabelle}\footnotesize
     #2
     \end{isabelle}
     \end{quote}}}
\newcommand{\Snippet}[1]{\ifcsname snippet--#1\endcsname\csname snippet--#1\endcsname\else\PackageError{}{No snippet '#1' defined.}{}\fi}
\input{snippets/snippets.out}

% \overfullrule=8pt

\begin{document}

\title{Efficient Verified (UN)SAT Certificate Checking}
% \titlerunning{Formalizing the Edmonds-Karp Algorithm}
% \subtitle{}

\author{Peter Lammich}

\institute{Technische Universit\"at M\"unchen, \email{lammich@in.tum.de}}

\maketitle

\begin{abstract}
We present an efficient formally verified checker for satisfiability and unsatisfiability certificates for 
Boolean formulas in conjunctive normal form. 
It utilizes a two phase approach: Starting from a DRAT certificate, the unverified generator computes an enriched certificate,
which is checked against the original formula by the verified checker.

Using the Isabelle/HOL Refinement Framework, we verify the actual implementation of the checker, specifying the semantics of the formula down to the integer sequence that represents it.

On a realistic benchmark suite drawn from the 2016 SAT competition, our approach is more than two times faster than the unverified standard tool drat-trim.
Additionally, we implemented a multi-threaded version of the generator, which further reduces the runtime.
\end{abstract}

% \begin{abstract}
% We present a formally verified and efficient checker for satisfiability and unsatisfiability certificates for 
% Boolean formulas in conjunctive normal form. While the satisfiability checker is trivial, our unsatisfiability checker
% is based on a two-step approach: Starting from a DRAT certificate, which is the de-facto standard for unsatisfiability certificates,
% we use an unverified tool to generate an enriched certificate, which is then checked against the original formula by the verified checker.
% 
% Our checker is proved correct with the Isabelle/HOL theorem prover, and its runtime is negligible compared to the time required for enriched certificate generation.
% 
% We benchmark our tools on all 104 problems that CryptoMiniSat + drat-trim verified as unsatisfiable in the 2016 SAT competition main track. 
% The time required for generating the enriched certificates plus running our verified checker is not more than the (unverified)
% standard tool drat-trim needs for checking the certificates.
% Moreover, we have implemented a multi-threaded version of our certificate generator. 
% On standard server hardware, we verify a DRAT certificate up to 9 times faster than (the single-threaded) drat-trim.
% \end{abstract}


\section{Introduction}
Modern SAT solvers are highly optimized and use complex algorithms and heuristics. This makes them prone to bugs.
Given that SAT solvers are used in software and hardware verification, a single bug in a SAT solver may 
invalidate the verification of many systems.
% Trust multipliers?

One measure to increase the trust in SAT solvers is to make them output a certificate, which is used to check 
the result of the solver by a simpler algorithm. 
Most SAT solvers support the output of a satisfying valuation of the variables as an easily checkable certificate for satisfiability.
Certificates for unsatisfiability are more complicated, and different formats have been proposed (\eg~\cite{SiBi06,WHH13,WHH14}).
Since 2013, the SAT competition~\cite{satcomp-2013} requires solvers to output unsat certificates.
Since 2014, only certificates in the DRAT format~\cite{WHH14} are accepted~\cite{satcomp-2014}.

% A DRAT proof consists of a list of clauses, called lemmas. For checking, the lemmas are added to the clauses of the 
% formula one by one, checking that adding each lemma preserves satisfiability. The last lemma in the list must be the empty clause, 
% thus witnessing unsatisfiability of the formula. Checking a lemma consists of checking whether it has the resolution asymmetric tautology (RAT) property~\cite{WHH14}
% \wrt the current clauses. Moreover, the DRAT format supports deletion of clauses that are no longer required to derive the empty clause.
% Mainly efficiency optimization?, can, in theory, enable drat-proofs

The standard tool to check DRAT certificates is drat-trim~\cite{WHH14,drat-trim-webpage}. 
It is a highly optimized C program with many features, including forward and backward checking mode, a satisfiability certificate checking mode,
and a feature to output reduced (trimmed) certificates.
% 
% It uses a two watched literals~\cite{MMZZ01} data structure for unit propagation, as well as some other optimizations,
% the most important one being backwards checking: By checking the lemmas from last to first, one can mark the lemmas 
% that where actually used in a check. When encountering an unmarked lemma, it can be skipped because it was not used 
% for any proof. This greatly reduces the runtime of the checker in practice, as many lemmas tend to be skipped~\cite{WHH14}.
% An additional optimization, called core-first unit propagation, uses a unit propagation algorithm that prefers marked lemmas over unmarked ones, trying to
% reduce the number of newly marked lemmas.
% 
However, the high degree of optimization and the wealth of features come at the price of code complexity, increasing the likelihood of bugs. And indeed, 
during our formalization of the RAT property, we realized that drat-trim was missing a crucial check, thus accepting (maliciously engineered) unsat certificates 
for satisfiable formulas. This bug has been confirmed by the authors, and is now fixed.
Moreover, we discovered several numeric and buffer overflow issues in the parser~\cite{drat-trim-issues}, which could lead to misinterpretation of the formula.
Thus, although being less complex than SAT solvers, efficient DRAT checkers are still complex enough to easily overlook bugs.\footnote{Unfortunately, the available version history of drat-trim~\cite{drat-trim-github} only dates back to October 2016. We can only speculate whether the discovered bugs were present in the versions used for the 2014 and 2016 SAT competitions.} 

One method to eliminate bugs from software is to conduct a machine-checked correctness proof. 
A common approach is to prove correct a specification in the logic of an interactive theorem prover, and then generate executable code from 
the specification. Here, code generation is merely a syntax transformation from the executable fragment of the theorem prover's logic to the target language.
Following the LCF approach~\cite{Gord00}, modern theorem provers like Isabelle~\cite{NPW02} and Coq~\cite{BeCa10} are explicitly designed to maximize their trustworthiness.
Unfortunately, the algorithms and low-level optimizations required for \emph{efficient} unsat certificate checking are 
hard to verify and existing approaches (\eg~\cite{DFM10,WHH13}) do not scale to large problems.

While working on the verification of an efficient DRAT checker, the author learned about GRIT, proposed by Cruz-Filipe et al.~\cite{CMS17}: 
They use a modified version of drat-trim to generate an enriched certificate from the original DRAT certificate. 
The crucial idea is to record the required unit propagations, such that the checker of the enriched certificate only needs 
to implement a check whether a clause is unit, instead of a fully fledged unit propagation algorithm.

Cruz-Filipe et al.\ formalize a checker for their enriched certificates in the Coq theorem prover~\cite{BeCa10}, and generate OCaml code from the formalization. 
However, their current approach still has some deficits:
GRIT only supports the less powerful DRUP fragment~\cite{WHH13} of DRAT, making it unsuitable for recent SAT solvers that output full DRAT~(\eg CryptoMiniSat, Riss6~\cite{SATCOMP16}).
Also, their checker does not consider the original formula, but assumes that the certificate correctly mirrors the formula. 
Moreover, they use unverified code to parse the certificate into the internal data structures of the checker.
Finally, their verified checker is quite slow: Checking a certificate requires roughly the same time as generating it, which effectively doubles the verification time.
However, an unverified implementation of their checker in C is two orders of magnitude faster.

% rely on an unverified parser to translate the 
% The generated code combined with an unverified parser, which parses the certificate into the internal data structures of the checker. 
% Moreover, their verified checker does not consider the original formula, but assumes that the certificate correctly mirrors the formula.
% 
% 
% The time for executing the verified checker roughly matches the time required to generate the enriched certificate, thus effectively doubling the verification time.
% %
% They also present an unverified checker written in C, which is two orders of magnitude faster than the verified one. The reason for the verified checker being slow is that
% they trade efficiency of the checker for simplicity of the formal correctness proof.
% %
% Finally, the GRIT format only supports the less powerful DRUP-fragment of DRAT, making it unsuitable for recent SAT solvers which output full DRAT~(\eg CryptoMiniSAT, Riss6~\cite{SATCOMP16}).

In this paper, we present enriched certificates for full DRAT, along with a checker whose correctness is formally verified down to the integer sequence 
representing the formula. 
The simple unverified parser that reads a formula into an integer array is written in Standard ML~\cite{MHMT97}, which guarantees that numeric and buffer overflows will 
not go unnoticed.

We use stepwise refinement techniques to obtain an efficient verified checker, and implement aggressive optimizations in the generator.
As a result, our tool chain (generation plus checking) is more than two times faster than drat-trim, with the additional benefit 
of providing strong formal correctness guarantees.
Another distinguishing is a multi-threaded mode for the generator, which allows us to trade hardware resources for additional speedup:
With 8 threads, our tool chain verifies a DRAT-certificate seven times (on average) faster than drat-trim.

Building on the technology of our verified unsat certificate checker, we also provide a verified sat certificate checker, 
obtaining a complete, formally verified, and fast SAT solver certification tool chain. 
Our tools, formalizations, and benchmark results are available online~\cite{GRAT-homepage}.

Independently to us, Cruz-Filipe et al.\ also extended their work to DRAT~\cite{CHHKS17}. 
Their certificate generator is still based on drat-trim, and first benchmarks indicate that
our approach might be significantly faster.\footnote{However, we expect that most of our optimizations can be transferred to their tools.}

% We have not yet benchmarked their implementation against ours.
% However, their certificate generator is based on drat-trim,
% % , and they report that certificate checking is almost as fast as certificate generation, 
% indicating that our approach might be significantly faster.

The rest of this paper is organized as follows: 
After briefly recalling the theory of DRAT certificates (\S\ref{sec:unsat_cert}), we introduce our enriched certificate format (\S\ref{sec:grat-format}).
We then give a short overview of the Isabelle Refinement Framework (\S\ref{sec:imp_ref_framework})
and describe its application to verifying our certificate checker (\S\ref{sec:grat_verified}). 
The paper ends with a brief description of our certificate generator (\S\ref{sec:gratgen}) and a report on the experimental evaluation 
of our tools (\S\ref{sec:benchmarks}).

\section{Unsatisfiability Certificates}\label{sec:unsat_cert}
We briefly recall the theory of DRAT unsatisfiability certificates. 
Let $V$ be a set of variable names. The set of \emph{literals} is defined as $L := V \dot\union \{\neg v \mid v\in V \}$.
We identify $v$ and $\neg\neg v$.
Let $F = C_1 \wedge \ldots \wedge C_n$ for $C_i \in 2^L$ be a formula in conjunctive normal form (CNF). 
$F$ is \emph{satisfied} by an \emph{assignment} $A : V \Rightarrow \textrm{bool}$ iff instantiating the variables in $F$ with $A$ yields a true (ground) formula.
We call $F$ \emph{satisfiable} iff there exists an assignment that satisfies $F$.

A clause $C$ is called a \emph{tautology} iff there is a variable $v$ with $\{v,\neg v\} \subseteq C$. Removing a tautology from a formula yields an equivalent formula.
In the following we assume that formulas do not contain tautologies.
The empty clause is called a \emph{conflict}. A formula that contains a conflict is unsatisfiable. 
A singleton clause $\{l\} \in F$ is called a \emph{unit clause}. Removing all clauses that contain $l$, and all literals $\neg l$ from $F$ yields an equisatisfiable formula.
Repeating this exhaustively for all unit clauses is called \emph{unit propagation}. When identifying formulas that contain a conflict, unit propagation is strongly normalizing. 
We name the result of unit propagation $F^{\textrm u}$, defining $F^{\textrm u} = \{\emptyset\}$ if unit propagation yields a conflict.

A DRAT certificate $\chi = \chi_1\ldots\chi_n$ with $\chi_i \in 2^L \mathbin{\dot\cup} \{ \textrm d C \mid C\in 2^L \}$
is a list of clause addition and deletion items.
The \emph{effect} of a (prefix of) a DRAT certificate is to add/delete the specified clauses to/from the original formula $F_0$, and apply unit propagation:
\begin{align*}
  \textrm{eff}(\varepsilon) &= (F_0)^\textrm{u}&
  \textrm{eff}(\chi C) &= (\textrm{eff}(\chi) \wedge C)^\textrm{u}&
  \textrm{eff}(\chi\textrm d C) &= \textrm{eff}(\chi) \setminus C
\end{align*}
where $F \setminus C$ removes one occurrence of clause $C$ from $F$. %, and does not change $F$ if there is no $C$ in $F$.
We call the clause addition items of a DRAT certificate \emph{lemmas}.

A DRAT certificate $\chi = \chi_1\ldots\chi_n$ is \emph{valid} iff $\textrm{eff}(\chi) = \{\emptyset\}$ and each lemma has the RAT property \wrt the effect of the previous items:
\[
  \textrm{valid}(\chi_1 \ldots \chi_n) := \forall 1\le i\le n.~\chi_i\in2^L \implies\textrm{RAT}( \textrm{eff}(\chi_1\ldots \chi_{i-1}), \chi_i )
\]
where a clause $C$ has the \emph{RAT} (\emph{resolution asymmetric tautology}) property \wrt formula $F$ (we write $\textrm{RAT}(F,C)$) iff either $C$ is empty and $F^{\textrm u}=\{\emptyset\}$,
or if there is a \emph{pivot literal} $l\in C$, such that for all \emph{RAT candidates} $D\in F$ with $\neg l \in D$, we have $(F \wedge \neg(C \cup D\setminus\{\neg l\}))^{\textrm u} = \{\emptyset\}$.
Adding a lemma with the RAT property to a formula preserves satisfiability, and so do unit propagation and deletion of clauses. Thus, existence of a valid DRAT certificate implies unsatisfiability of the original formula.

% The empty clause having RAT property is equivalent to unit propagation on the current effect finding a conflict. We call such a conflict a \emph{root conflict}.
% Thus, instead of waiting for the empty clause to be added, a RAT certificate checker can perform unit propagation after adding each lemma, 
% and stop as soon as this finds a conflict.

A strictly weaker property than RAT is \emph{RUP} (\emph{reverse unit propagation}): A lemma $C$ has the RUP property \wrt formula $F$ iff $(F \wedge \neg C)^{\textrm u} = \{\emptyset\}$.
Adding a lemma with the RUP property yields an equivalent formula. The predecessor of DRAT is DRUP~\cite{HHW13}, which admits only lemmas with the RUP property.

Checking a lemma for RAT is much more expensive than checking for RUP, as the clause database must be searched for candidate clauses,
performing a unit propagation for each of them. Thus, practical DRAT certificate checkers first perform a RUP check on a lemma, and only if 
this fails they resort to a full RAT check. Exploiting that $(F\wedge\neg(C\union D))^\textrm{u}$ is equivalent to $((F \wedge \neg C)^\textrm{u} \wedge \neg D)^\textrm{u}$,
the result of the initial unit propagation from the RUP check can even be reused.
Another important optimization is \emph{backward checking}~\cite{GoNo03,HHW13}: The lemmas are processed in reverse order, marking the lemmas that are actually needed 
in unit propagations during RUP and RAT checks. Lemmas that remain unmarked need not be processed at all. To further reduce the number of marked lemmas, 
\emph{core-first} unit propagation~\cite{WHH14} prefers marked unit clauses over unmarked ones.

In practice, DRAT certificate checkers spend most time on unit propagation\footnote{Our profiling data indicates that, depending on the problem, up to 93\% of the time is spent for unit propagation.}, for which highly optimized implementations of rather complex algorithms 
are used (\eg drat-trim uses a two watched literals algorithm~\cite{MMZZ01}).
Unfortunately, verifying such highly optimized code in a proof assistant is a major endeavor.
Thus, a crucial idea is to implement an unverified tool that enriches the certificate with additional information that can be used for simpler and more efficient verification.
For DRUP, the GRIT format has been proposed recently~\cite{CMS17}. 
It stores, for each lemma, a list of unit clauses in the order they become unit, followed by a conflict clause.
Thus, unit propagation is replaced by 
simply checking whether a clause is unit or conflict. A modified version of drat-trim is used to generate a GRIT certificate from the original DRAT certificate.


\section{The GRAT Format}\label{sec:grat-format}
The first contribution of this paper is to extend the ideas of GRIT from DRUP to DRAT.
To this end, we define the GRAT format. 
Like for GRIT, each clause is identified by a unique positive ID. 
The clauses of the original formula implicitly get the IDs $1\ldots N$. The lemma IDs explicitly occur in the certificate, and must be strictly ascending.

For memory efficiency reasons, we store the certificate in two parts: The lemma file contains the lemmas, and is stored in DIMACS format.
During certificate checking, this part is entirely loaded into memory.
The proof file contains the hints and instructions for the certificate checker. It is not completely loaded into memory, but only streamed during checking.

The proof file is a binary file, containing a sequence of 32 bit signed integers stored in 2's complement little endian format. 
The sequence is reversed (or the file is streamed backwards), and then interpreted according to the following grammar:
\begin{lstlisting}[language={},columns={[c]fullflexible},literate={}]
  proof      ::= rat-counts item* conflict
  literal    ::= int32 != 0
  id         ::= int32 > 0
  count      ::= int32 > 0
  rat-counts ::= 6 (literal count)* 0
  item       ::= unit-prop | deletion | rup-lemma | rat-lemma
  unit-prop  ::= 1 id* 0
  deletion   ::= 2 id* 0
  rup-lemma  ::= 3 id id* 0 id
  rat-lemma  ::= 4 literal id id* 0 cand-prf* 0
  cand-prf   ::= id id* 0 id
  conflict   ::= 5 id
\end{lstlisting}

The checker maintains a \emph{clause map} that maps IDs to clauses, and a \emph{partial assignment} that maps variables to true, false, or undecided. 
Partial assignments are extended to literals in the natural way.
Initially, the clause map contains the clauses of the original formula, and the partial assignment maps all variables to undecided.
Then, the checker iterates over the items of the proof, processing each item as follows:
\begin{description}
    \item[rat-counts] This item contains a list of pairs of literals and the number how often they are used in RAT proofs. 
      This map allows the checker to maintain lists of RAT candidates for the relevant literals, instead of gathering the 
      possible RAT candidates by iterating over the whole clause database for each RAT proof, which is expensive.
      Literals that are not used in RAT proofs at all do not occur in the list. This item is the first item of the proof.
  \item[unit-prop] 
    For each listed clause ID, the corresponding clause is checked to be unit, and the unit literal is assigned to true.
    Here, a clause is unit if the unit literal is undecided, and all other literals are assigned to false.
  \item[deletion] The specified IDs are removed from the clause map.
  \item[rup-lemma] The item specifies the ID for the new lemma, which is the next unprocessed lemma from the lemma file, a list of unit clause IDs, and a conflict clause ID.
      First, the literals of the lemma are assigned to false. The lemma must not be blocked, \ie none of its literals may be already assigned to true\footnote{Blocked lemmas are useless for unsat proofs, such that there is no point to include them into the certificate.}.
        Note that assigning the literals of a clause $C$ to false is equivalent to adding the conjunct $\neg C$ to the formula. 
      Second, the unit clauses are checked and the corresponding unit literals are assigned to true.
      Third, it is checked that the conflict clause ID actually identifies a conflict clause, \ie that all its literals are assigned to false.
      Finally, the lemma is added to the clause-map and the assignment is rolled back to the state before checking of the item started.
    \item[rat-lemma] The item specifies a pivot literal $l$, an ID for the lemma, an initial list of unit clause IDs, and a list of
      candidate proofs. 
      First, as for \lsti{rup-lemma}, the literals of the lemma are assigned to false and the initial unit propagations are performed. 
      Second, it is checked that the provided RAT candidates are exhaustive, and the corresponding \lsti{cand-prf} items are processed:
      A \lsti{cand-prf} item consists of the ID of the candidate clause $D$, a list of unit clause IDs, and a conflict clause ID.
      To check a candidate proof, the literals of $D\setminus\{\neg l\}$ are assigned to false, the listed unit propagations are performed, and the conflict clause is 
      checked to be actually conflict. Afterwards, the assignment is rolled back to the state before checking the candidate proof.
      Third, when all candidate proofs have been checked, the lemma is added to the clause map and the assignment is rolled back.

      To simplify certificate generation in backward mode, we allow candidate proofs referring to arbitrary, even invalid, clause IDs. Those proofs must be ignored by the checker.
    \item[conflict] This is the last item of the certificate. It specifies the ID of the \emph{root conflict} clause, \ie the conflict found by unit propagation after adding the last 
    lemma of the certificate. It is checked that the ID actually refers to a conflict clause.
\end{description}


% 
% 
% A unit propagation item (1) indeicates a unit propagation to be performed on 
% 
% 
% A unit propagation item (1) indicates a unit propagation to be performed on the current formula,
% a deletion item (2) specifies a clause ID to be deleted. 
% A rup-lemma (3) specifies the lemma ID, followed by the literals of the lemma, followed by a list of unit clause identifiers and a conflict clause.
% A rat-lemma (4) specifies the ID, the rat-literal, the literals of the lemma, an initial list of unit clause identifiers, and a list of candidate proofs.
% A candidate proof contains the ID of the candidate clause, followed by a list of unit clause IDs and a conflict clause. To simplify certificate generation in backward mode, we allow candidate IDs to be unassigned or refer to deleted clauses. Such candidate proofs must be ignored by the GRAT checker.
% The conflict item (5) specifies the ID of the root conflict clause. The rat-counts item (6) contains a list of pairs of literals and the number how often they are used in RAT proofs. 
% Literals that are not used in RAT proofs at all do not occur in the list.
% In order to allow GRAT certificates to be generated during backward checking without buffering, we store the lemmas of the enriched certificate in reverse order.
% Hence, the conflict item is always the first item in the file, and the RAT-counts item is always last.
% 
% An efficient but yet simple algorithm to check a GRAT certificate can be implemented based on a \emph{partial assignment} $A: V \to \{1,-1,0\}$
% that maps variables to true ($1$), false ($-1$), or undecided ($0$). We extend $A$ to literals by $A(\neg v) := -A(v)$.
% 
% The checker maintains a map from IDs to clauses, and a current assignment. Instead of updating the clauses themselves, it only updates the assignment:
% To start a RUP or RAT check, the current formula $F$ must be updated to $F \wedge \neg C$. For  $C=\{l_1,\ldots,l_n\}$, this is
% equivalent to $F \wedge \{\neg l_1\} \wedge \ldots \wedge \{\neg l_n$\}, which, due to unit propagation, amounts to 
% updating the assignments of $l_1,\ldots,l_n$ to false.
% A clause is unit if it has exactly one undecided literal, and all other literals are assigned to false, and conflict if all its literals are assigned to false.
% Unit propagation iterates over the list of unit clauses, checking each clause to be unit, and assigning the undecided literal to true.
% After checking a lemma, the current assignment is rolled back to the original state. This is achieved by recording the variables that are assigned during the check, 
% and then unassigning them.
% % In Section~\ref{sec:grat_verified} we describe our GRAT checker implementation and its verification.


\section{Program Verification with Isabelle/HOL}\label{sec:imp_ref_framework}
Isabelle/HOL~\cite{NPW02} is an interactive theorem prover for higher order logic. Its design features the LCF approach~\cite{Gord00}, where 
a small logical inference kernel is the only code that can produce theorems. Bugs in the non-kernel part may result in failure to 
prove a theorem, but never in a false proposition being accepted as a theorem.
Isabelle/HOL includes a code generator~\cite{Haft09,HaNi10,HKKN13} that translates the executable fragment of HOL to various functional programming languages, 
currently OCaml, Standard ML, Scala, and Haskell.
Via Imperative HOL~\cite{BKHEM08}, the code generator also supports imperative code, modeled by a heap monad inside the logic.

A common problem when verifying efficient implementations of algorithms is that implementation details tend to obfuscate the proof and increase its complexity. 
Hence, efficiency of the implementation is often traded for simplicity of the proof.
A well-known approach to this problem is stepwise refinement~\cite{Wirth71,Back78,BaWr98}, where an abstract version of the algorithm is refined towards 
an efficient implementation in multiple correctness preserving steps.
The abstract version focuses on the 
algorithmic ideas, leaving open the exact implementation, while the refinement steps focus on more and more concrete implementation aspects.
This modularizes the correctness proof, and makes verification of complex algorithms manageable in the first place.

For Isabelle/HOL, the Isabelle Refinement Framework~\cite{LaTu12,La13,La15,La16} provides a powerful stepwise refinement tool chain, 
featuring a nondeterministic shallowly embedded programming language~\cite{LaTu12}, a library of efficient collection data structures and generic algorithms~\cite{LL10,La15,La16},
and convenience tools to simplify canonical refinement steps~\cite{La13,La15}. It has been used for various software verification projects (\eg \cite{La14,LaSe16,LaNe15}), 
including a fully fledged verified LTL model checker~\cite{ELNN13,BrLa16}.

\section{A Verified GRAT Certificate Checker}\label{sec:grat_verified}
We give an overview of our Isabelle/HOL formalization of a GRAT certificate checker (\cf Section~\ref{sec:grat-format}).
We use the stepwise refinement techniques provided by the Isabelle Refinement Framework to verify an efficient implementation at manageable proof complexity.

Note that we display only slightly edited Isabelle source text, and try to explain its syntax as far as needed to get a basic understanding.
Isabelle uses a mixture of common mathematical notations and Standard ML~\cite{MHMT97} syntax (\eg there are algebraic data types, function application is written as 
\isai$f x$, functions are usually curried, \eg \isai$f x y$, and abstraction is written as \isai$\<lambda>x y. t$).

% (\eg there are algebraic data types, function application is written as 
% \isai$f x$, functions are usually curried, \eg \isai$f x y$, and abstraction is written as \isai$\<lambda>x y. t$) and standard mathematical notations like 
% quantifiers and logical connectives (\eg \isai$\<forall>, \<and>, ==>$). Due to the tight page limit, we can only display small excerpts from the original formalization.
% For details, we refer the reader to the complete formalization~\cite{??}.

\subsection{Syntax and Semantics of Formulas}
The following Isabelle text specifies the abstract syntax of CNF formulas:
\begin{lstlisting}
  datatype 'a literal = Pos 'a | Neg 'a
  type_synonym 'a clause = "'a literal set"
  type_synonym 'a cnf = "'a clause set"
\end{lstlisting}
We abstract over the type \isai$'a$ of variables, use an algebraic data type to specify positive and negative literals, and model clauses 
as sets of literals, and a CNF formula as set of clauses. 
% Note that we will refine this abstract representation to an array of integers
% 
% 
% Note that, ultimately, the formula will be represented as an array of integers. 
% However, this abstract specification is much easier to work with on an abstract level, and we will use data refinement to make our final algorithm 
% actually use an array of integers.

A partial assignment has 
type \isai{'a => bool option}, which is abbreviated as \isai{'a -` bool} in Isabelle. 
It maps a variable to \isai{None} for undecided, or to \isai{Some True} or \isai{Some False}.
We specify the semantics of literals and clauses as follows:
\begin{lstlisting}
  primrec sem_lit' :: "'a literal => ('a-`bool) -` bool" where
    "sem_lit' (Pos x) A = A x" | "sem_lit' (Neg x) A = map_option Not (A x)"
  definition "sem_clause' C A ==
    if (\<exists>l\<in>C. sem_lit' l A = Some True) then Some True
    else if (\<forall>l\<in>C. sem_lit' l A = Some False) then Some False
    else None"
\end{lstlisting}
Note that we omitted the type specification for \isai{sem_clause'}, in which case Isabelle automatically infers the most general type.

For a fixed formula $F$, we define the \emph{models} induced by a partial assignment to be all total extensions that satisfy the formula. 
We define two partial assignments to be \emph{equivalent} if they induce the same models.

\subsection{Unit Propagation and RAT}
We define a predicate to state that, \wrt a partial assignment $A$, a clause $C$ is unit, with unit literal $l$:
\begin{lstlisting}
  definition "is_unit_lit A C l 
    == l\<in>C \<and> sem_lit' l A = None \<and> sem_clause' (C-{l}) A = Some False"
\end{lstlisting}
Assigning a unit literal to true yields an equivalent assignment:
\begin{lstlisting}
  lemma unit_propagation:
    assumes "C\<in>F" and "is_unit_lit A C l"
    shows "equiv' F A (assign_lit A l)"
\end{lstlisting}
In Isabelle, all variables that occur free in a lemma (here: \isai{C,F,A,l}) are implicitly universally quantified.

Having formalized the basic concepts, we can show the essential lemma that justifies RAT (\cf Section~\ref{sec:unsat_cert}):
\begin{lstlisting}
  lemma abs_rat_criterion:
    assumes "l\<in>C" and "sem_lit' l A \<noteq> Some False"
    assumes "\<forall>D\<in>F. neg_lit l \<in> D ==> implied_clause F A (C\<union>(D-{neg_lit l}))"  
    shows "redundant_clause F A C"
\end{lstlisting}
Where a clause is \emph{implied} if it can be added to the formula without changing the models, 
and it is \emph{redundant} if adding the clause preserves satisfiability (but not necessarily the models).


% 
% 
% 
% Thus, this lemma states that if $l$ is a literal of the clause $C$ that is not assigned to false\footnote{The check for this side condition was actually omitted in drat-trim, making it unsound (\cf introduction).}, and for all \emph{candidate clauses} $D$ that contain $\neg l$,
% the the clause $C(D\setminus\neg l)$ is implied, then the clause $C$ is redundant, \ie we can add it to the formula without changing satisfiability.

\subsection{Abstract Checker Algorithm}
Having formalized the basic theory of CNF formulas \wrt partial assignments, we can specify an abstract version of the certificate checker algorithm.
Our specifications live in an exception monad stacked onto the nondeterminism monad of the Isabelle Refinement Framework.
Exceptions are used to indicate failure of the checker, and are never caught.
%
We only prove soundness of our checker, \ie that it does not accept satisfiable formulas.
Our checker actually accepted all certificates in our benchmark set (\cf Section~\ref{sec:benchmarks}), yielding 
an empirical argument that it is sufficiently complete.

At the abstract level, we model the proof as a stream of integers. On this, we define functions \isai$parse_id$ and \isai$parse_lit$ that fetch an 
element from the stream, try to interpret it as ID or literal, and fail if this is not possible.
The state of the checker is a tuple \isai$(last_id,CM,A)$. To check that the lemma IDs are strictly ascending, \isai{last_id} stores the ID of the last processed lemma.
The \emph{clause map} \isai{CM} contains the current formula as a mapping from IDs to clauses, and also maintains the RAT candidate lists. Finally, \isai$A$ is the current assignment.


% First, we show the specification of the function that pulls a clause from the stream, assigns its literals to false, and checks whether the clause is blocked. 
% \footnote{This combination of functionality is clearly inspired by our later implementation. 
% Note however that the refinement framework would also admit to combine this functionality in a later refinement step with only slight proving overhead.}
% \begin{lstlisting}
% definition "parse_check_blocked A it == doE {eassert (it_invar it); espec 
%   (\<lambda>_. True) 
%   (\<lambda>(t,C,A',it'). (\<not>t --> (\<exists>l. lz_string 0 it l it' \<and> it_invar it' 
%           \<and> C=clause_\<alpha> l \<and> \<not>is_blocked A C \<and> A' = and_not_C A C)))}"
% \end{lstlisting}
% The assertion expresses the precondition that the iterator must be valid. 
% The first part \isai$\<lambda>_. True$ is the postcondition in case of an exception.
% The second part specifies the postcondition for the normal return value, which is a flag $t$, the parsed clause $C$, the new assignment $A'$, and the new iterator \isai{it'}.
% Here, \isai$lz_string 0$ is the predicate for a zero terminated sequence $l$ of integers, from position \isai$it$ to position \isai$it'$, and \isai{clause_\<alpha>} 
% maps sequences of integers to actual clauses (As we parsed a null-terminated sequence, we know that every item is non-zero, and thus can be interpreted as a literal).
% 
% Note that weaker specifications are easier to implement. As we are only interested in soundness, we can afford 
% exceptions to be thrown unconditionally, as indicated by the \isai$\<lambda>_. True$. Moreover, we can afford to report arbitrary clauses as 
% blocked, without any constraints on the returned clause, assignment, or iterator (they will be ignored). This is expressed by the implication \isai{\<not>t --> ...}.
% 

As first example, we present the abstract algorithm that is invoked after reading the item-type of a \lsti{rup-lemma} item (\cf Section~\ref{sec:grat-format}),
\ie we expect a sequence of the form \lstinline[language={},literate={}]{id id* "0" id}.
\begin{lstlisting}[numbers=left, xleftmargin=2em]
  "check_rup_proof == \<lambda>(last_id,CM,A_0) it prf. do {
    (i,prf) <- parse_id prf;
    check (i>last_id);
    (C,A',it) <- parse_check_blocked A_0 it;
    (A',prf) <- apply_units CM A' prf;
    (confl_id,prf) <- parse_id prf;
    confl <- resolve_id CM confl_id;
    check (sem_clause' confl A' = Some False);
    CM <- add_clause i C CM;
    return ((i,CM,A_0),it,prf)
  }"
\end{lstlisting}
We use do-notation to conveniently express monad operations. 
First, the lemma ID is pulled from the proof stream (line 2) and checked to be greater than \isai{last_id} (3).
The \isai$check$ function throws an exception unless the first argument evaluates to true.
Next, \isai{parse_check_blocked} (4) parses the next lemma from the lemma file, checks that it is not blocked, and assigns its literals to false.
Then, the function \isai$apply_units$ (5) pulls the unit clause IDs from the proof stream, checks that they are actually unit, and assigns the unit literals to true.
Finally, we pull the ID of the conflict clause (6), obtain the corresponding clause from the clause map (7), check that it is actually conflict (8), and add the lemma to the clause map (9).
We return (10) the lemma ID as new last ID, the new clause map, and the \emph{old} assignment, as the changes to the assignment are local and must be backtracked before checking
the next clause. Additionally, we return the new position in the lemma file (\isai$it$) and in the proof stream (\isai$prf$).
Note that this abstract specification contains non-algorithmic parts: For example, in line 8, we check for the semantics of the conflict clause to be \isai{Some False}, without
specifying how to implement this check.
%
We prove the following lemma for \isai{check_rup_proof}:
\begin{lstlisting}
lemma check_rup_proof_correct: 
  assumes "invar (last_id,CM,A)"
  shows "check_rup_proof (last_id,CM,A) it prf
      \<le> spec True (\<lambda>((last_id',CM',A'), it', prf'). 
          invar (last_id',CM',A') \<and> (sat' (cm_F CM) A ==> sat' (cm_F CM') A'))"
\end{lstlisting}
Here, \isai{spec \<Phi> \<Psi>} describes the postcondition \isai$\<Phi>$ in case of an exception, and the postcondition \isai$\<Psi>$ for a normal result.
As we only prove soundness of the checker, we use \isai$True$ as postcondition for exceptions. For normal results, 
we show that an invariant on the state is preserved, and that the resulting formula and partial assignment is satisfiable if the original formula and partial assignment was.

Finally, we present the specification of the checker's main function:
\begin{lstlisting}[numbers=left, xleftmargin=2em]
definition "verify_unsat F_begin F_end it prf == do {
  let A = \<lambda>_. None;
  (CM,prf) <- init_rat_counts prf;
  (CM,last_id) <- read_cnf F_end F_begin CM;
  let s = (last_id,CM,A);
  (so,_) <- while (\<lambda>(so,it). so\<noteq>None) (\<lambda>(so,it). 
  do {
    let (s,it,prf) = the so;
    check_item s it
  }) (Some (s,it,prf));
}"
\end{lstlisting}
The parameters \isai{F_begin} and \isai{F_end} indicate the range that hold the representation of the formula, \isai{it} points to the 
first lemma, and \isai{prf} is the proof stream.
After initializing the assignment (line 2, all variables undecided), the RAT literal counts are read (3), and the formula is parsed into the clause map (4).
Then, the function iterates over the proof stream and checks each item (6--10), until the formula has been certified. (or an exception terminates the program)
Here, the checker's state is wrapped into an option type, where \isai{None} indicates that the formula has been certified. 
%
Correctness of the abstract checker is expressed by the following lemma:
\begin{lstlisting}
lemma verify_unsat_correct: "
  assumes "seg F_begin lst F_end"
  shows verify_unsat F_begin F_end it prf
      \<le> spec True (\<lambda>_. F_invar lst \<and> \<not>sat (F_\<alpha> lst))"
\end{lstlisting}
Intuitively, if the range from \isai{F_begin} to \isai{F_end} is valid and contains the sequence \isai{lst},
and if \isai{verify_unsat} returns a normal value, then \isai{lst} represents a valid CNF formula (\isai{F_invar lst}) 
that is unsatisfiable (\isai{\<not>sat (F_\<alpha> lst)}). Note that the correctness statement does not depend on the 
lemmas (\isai{it}) or the proof stream (\isai{prf}). This will later allow us to use an optimized (unverified) 
implementation for streaming the proof, without impairing the formal correctness statement.

\subsection{Refinement towards an Efficient Implementation}    
The abstract checker algorithm that we described so far contains non-algorithmic parts and uses abstract types like sets.
Even if we could extract executable code, its performance would be poor: For example, we model assignments as functions. Translating 
this directly to a functional language results in assignments to be stored as long chains of function updates with worst-case linear time lookup.

We now show how to refine the abstract checker to an efficient algorithm, replacing the specifications by actual algorithms,
and the abstract types by efficient data structures.
The refinement is done in multiple steps, where each step focuses on different aspects of the implementation. 
Formally, we use a \emph{refinement relation} that relates objects of the refined type (\eg a hash table) to 
objects of the abstract type (\eg a set). In our framework, refinement is expressed 
by propositions of the form \isai{(c,a)\<in>R ==> g c \<le>\<Down>S (f a)}: if the concrete argument \isai$c$ is related to the abstract argument \isai$a$ by \isai$R$, then the result of the concrete
algorithm \isai$g c$ is related to the result of the abstract algorithm \isai$f a$ by \isai$S$. Moreover, if the concrete algorithm throws an exception, 
the abstract algorithm must also throw an exception.

In the first refinement step, we record the set of variables assigned during checking a lemma, 
and use this set to reconstruct the original assignment from the current assignment after the check. 
This saves us from copying the whole original assignment before each check.
Formally, we define an \emph{$A_0$-backtrackable assignment} to be an assignment $A$ together with a set of assigned variables $T$, such that
unassigning the variables in $T$ yields $A_0$. The relation \isai{bt_assign_rel} relates $A_0$-backtrackable assignments to plain assignments:
\begin{lstlisting}
"bt_assign_rel A_0 == { ((A,T),A) | A T. T \<subseteq> dom A \<and> A_0 = A|`(-T) }"
\end{lstlisting}
We define \isai{apply_units_bt}, which operates on $A_0$-backtrackable assignments. If applied 
to assignments \isai$(A',T)$ and \isai$A$ related by \isai{bt_assign_rel A_0}, and to the same proof stream position \isai$prf$, then the results of 
\isai{apply_units_bt} and \isai{apply_units} are related by \isai{bt_assign_rel A_0 \<times> Id}, \ie the returned assignments 
are again related by \isai{bt_assign_rel A_0}, and the new proof stream positions are the same (related by \isai{Id}):
\begin{lstlisting}
lemma apply_units_bt_refine: assumes "((A',T),A)\<in>bt_assign_rel A_0"
  shows "apply_units_bt CM A' T prf 
    \<le> \<Down>(bt_assign_rel A_0 \<times> Id) (apply_units CM A prf)"
\end{lstlisting}

In the next refinement step, we implement clauses by iterators pointing to the start of a null-terminated sequence of integers.
Thus, the clause map will only store iterators instead of (replicated) clauses. 
Now, we can specify algorithms for functions on clauses. For example, we define:
\begin{lstlisting}
  "check_conflict_clause1 A cref == iterate_clause cref (\<lambda>l _. do {
    check (sem_lit' l A = Some False)
  }) ()"
\end{lstlisting}
\ie we iterate over the clause, checking each literal to be false. We show:
\begin{lstlisting}
lemma check_conflict_clause1_refine: assumes CR: "(cref,C)\<in>cref_rel"
  shows "check_conflict_clause1 A cref 
         \<le>\<Down>Id (check (sem_clause' C A = Some False))"
\end{lstlisting}
where the relation \isai{cref_rel} relates iterators to clauses.

In the next refinement step, we introduce efficient data structures. For example, we implement the iterators by indexes 
into an array of integers that stores both the formula and the lemmas.
For many of the abstract types, we use general purpose data structures from the Isabelle Refinement Framework~\cite{La15,La16}.
For example, we refine assignments to arrays, using the \isai{array_map_default} data structure, which implements functions of type \isai{nat=>'a option} by
arrays of type \isai$'b array$. It is parameterized by a relation \isai$R : ('b\<times>'a) set$ and a default concrete element \isai$d$ that does not correspond to 
any abstract element (\isai$\<nexists>a. (d,a)\<in>R$). The implementation uses \isai$d$ to represent the abstract value \isai$None$.
We define:
\begin{lstlisting}
definition "vv_rel == {(1, False), (2, True)}"
definition "assignment_assn == amd_assn 0 id_assn (pure vv_rel)"
\end{lstlisting}
\ie we implement \isai{Some False} by $1$, \isai{Some True} by $2$, and \isai$None$ by $0$.
Here, \isai$amd_assn$ is the relation of the \isai$array_map_default$ data structure\footnote{The name suffix \isai$_assn$ instead of \isai$_rel$ 
indicates that the data structure may be stored on the heap.}.
The refined programs and refinement theorems in this step are automatically generated by the Sepref tool~\cite{La15}. For example, the command
\begin{lstlisting}
  sepref_definition check_rup_proof3 is "check_rup_proof2"
    :: "cdb_assn$^k$ * state_assn$^d$ * it_assn$^k$ * prf_assn$^d$ 
        -> error_assn + state_assn \<times> it_assn \<times> prf_assn"
\end{lstlisting}
takes the definition of \isai{check_rup_proof2}, generates a refined version, and proves the corresponding refinement theorem.
The first parameter is refined \wrt \isai{cdb_assn} (refining the set of clauses into an array), 
the second parameter is refined \wrt \isai{state_assn} (refining the clause map and the assignment into arrays), the third parameter is refined 
\wrt \isai{it_assn} (refining the iterator into an array index), and the fourth parameter is refined \wrt \isai{prf_assn} (refining the stream position). 
Exception results are refined \wrt \isai{error_assn} (basically the identity relation), and normal results are refined \wrt \isai{state_assn}, \isai{it_assn}, and \isai{prf_assn}.
The $x^d$ and $x^k$ annotations indicate whether the generated function may overwrite a parameter ($d$ like {\em destroy}) or not ($k$ like {\em keep}). 

By combining all the refinement steps and unfolding some definitions, we prove the following correctness theorem for the implementation of our checker:
\begin{lstlisting}
theorem verify_unsat_impl_correct: "
  <DBi |->$_a$ DB> 
    verify_unsat_impl DBi F_end it prf
  <\<lambda>result. DBi |->$_a$ DB * \<up>(\<not>isl result ==> formula_unsat_spec DB F_end)>"
\end{lstlisting}
This Hoare triple states that if \isai$DBi$ points to an array holding the elements \isai$DB$,
and we run \isai$verify_unsat_impl$, the array will be unchanged, and if the return value is no exception,
the formula represented by the range \isai$1...F_end$ in the array is unsatisfiable. 
We have experimented with many equivalent formulations of \isai{formula_unsat_spec}, trying to reduce the \emph{trusted base}, 
\ie the concepts and definitions the specification depends on. A concise one is:
\begin{lstlisting}
definition assn_consistent :: "(int => bool) => bool"
  where "assn_consistent \<sigma> = (\<forall>x. x\<noteq>0 ==> \<not> \<sigma> (-x) = \<sigma> x)"
definition "formula_unsat_spec DB F_end == (
  let lst = tl (take F_end DB) in
    1 < F_end \<and> F_end \<le> length DB \<and> last lst = 0 
    \<and> (\<nexists>\<sigma>. assn_consistent \<sigma> \<and> (\<forall>C\<in>set (tokenize 0 lst). \<exists>l\<in>set C. \<sigma> l)))"
\end{lstlisting}
Here, a \emph{consistent assignment} is a mapping from integers to Booleans, such that a negative value is mapped to the opposite as its absolute value.
The specification then defines \isai$lst$ to be the elements \isai$1,...,F_end$ of the array\footnote{Element $0$ is used as a guard in our implementation.}, and
states that \isai$F_end$ is in bounds, the last element of \isai$lst$ is a null, and that there is \emph{no} assignment such that each clause contains a literal assigned to true. 
We define \isai{tokenize 0 lst} to be the unique list of lists of non-null integers whose concatenation as null-terminated lists yields \isai{lst}.
This way, we specify an unsatisfiable formula down to the list of integers that represents it, only using basic list functions. 
The last section of the proof outline of our formalization~\cite{GRATchk-proof-outline} contains a detailed discussion of the correctness theorem.

The final step to a verified efficient unsat checker is to use Isabelle/HOL's code generator to extract Standard ML code for \isai{verify_unsat_impl} and to
link this code with a small (40 LOC) parser to read the formula (and the lemmas) into an array. 
Moreover, we implement a buffered reader for the proof file. This, however, does not affect the correctness statement, which is 
valid for all proof stream implementations. The resulting program is compiled with MLton~\cite{MLton}.

% Basics: formula, semantics, sat
%   This is also important as it is the trusted part of our specification
% 
% unit_propagation lemma:
%   prereq: partial assignment, equiv', is_unit_lit
%   
% abs_rat_criterion:
%   prereq: implied_clause, redundant_clause
% 
% check-rup-proof:
%   prereq:
%     exception-monad (CHECK-command), 
%     parser abstraction, 
%     blocked clause, 
%     clause-map (id->clause and rat-literals. Note, we do not exploit the counts, but initially fix literals to be mapped)
%   correctness statement of check-rup-proof?
%   
% verify-unsat (main function)  
%   explain main invariant component for loop: sat (F_\<\alpha> lst) ⟶ sat' (cm_F CM) A,
%     where lst holds the original CNF formula, and CM is the current clause map, and A the current partial assignment.
%   correctness statement!
%     
% \subsection{Refinement towards Efficient Implementation}    
% 
% Steps:
%   backtracking - instead of re-using the original assignment after unit propagations (\eg in check-rup-proof), 
%       we store all assigned literals in a list, and then unassign these literals. 
%       This will later allow us to use destructive updates for the assignment, which enables an efficient implementation as an array.
% 
%   clause references - instead of modeling clauses as sets of literals, we no model them as an iterator pointing to the starting position of the clause.
%     Thus, the clause map holds only pointers to the clause, instead of replicating the whole clause. Moreover, at this level, we implement the algorithms over clauses,
%     which have been left unspecified at the more abstract levels. \eg: check-conflict-clause-1, and refinement lemma.
% 
%   imperative data structures - we refine the algorithm to use imperative data structures.
%     In this process, we use the sepref synthesis tool, which can do some of the refinements automatically, 
%     given a specification which data structures to be used.
%     
%     At this level, we also instantiate the iterators, making them indexes into one big array holding the formula and the proof. 
%     This refinement also contains some amount of technical boilerplate, mainly due to Isabelle's 
%     code generator not working properly with locales (we have to pull all specifications out of locales before generating code: Note, this is done completely inside the logic!),
%     and the Sepref tool not yet working with the exception monad (we have to unfold the exception monad combinators, before we can use Sepref).
%     
%     E.g.: Implementation of assignments as an array, using general purpose data structure array_map_default:
%       definition "vv_rel == {(1::nat, False), (2, True)}"
%       definition "assignment_assn == amd_assn 0 id_assn (pure vv_rel)"
%   
%   code generation:
%     Straightforward, by invoking the Isabelle code generator on the definition created in the last refinement step.
%     Note: only in this step, one has to trust the code generator and its setup for imperative/HOL, 
%         that it correctly translates the primitives (functional program structure and array operations) to the target language.
%         All the previous refinements have been completely inside the logic of Isabelle/HOL.
%   
%   Final correctness theorem:
%     The correctness theorem shows that our concrete program (from which we extracted the code) behaves as expected:
%       lemma verify_unsat_impl_wrapper_correct.
%       
%     Note on formula_unsat_spec:
%       We have experimented with many equivalent versions of this specification, trying to reduce the 
%       number of concepts and definitions it depends on, thus keeping the trusted base small.
%       We present the following version here, which only depends on the concepts of tokenizing a list and of a consistent assignment, 
%       encoded as function from integers to Booleans:
%         assn_consistent and formula_unsat_spec_alt'
%       
%     Note on partial correctness: 
%       this lemma only states that a successful check implies an unsatisfiable formula. In case of an unsuccessful check (as indicated by isl r),
%       the lemma makes no statement at all. That is, we have only proved that our checker won't accept invalid certificates, but not that 
%       it accepts all valid certificates. To this end, we rely on experimental evaluation, where our checker accepted all certificates (cf Section~\ref{??}).
%     
%     
    
\section{Multithreaded Generation of Enriched Certificates}\label{sec:gratgen}
In order to generate GRAT certificates, we extend a DRAT checker algorithm 
to record the unit clauses that lead to a conflict when checking each lemma. 
% All optimizations for DRAT checkers, like backward checking and core-first unit propagation are compatible with enriched certificate generation.

Our certificate generator started as a reimplementation of the backward mode of drat-trim~\cite{WHH13,drat-trim-webpage} in \CC, to 
which we then added GRAT certificate generation. 
% We decided to do a re-implementation instead of modifying the existing code, as we only 
% wanted to include the absolutely necessary features, keeping the code small and clear, and thus enable further optimizations to be encoded at lower effort.
As the certificate generator is not part of the trusted code base, we could afford to add aggressive novel optimizations:
We maintain separate watchlists for marked and unmarked lemmas, which allows a more efficient implementation of core-first unit propagation.
Moreover, we detect runs of lemmas with the same pivot element, which allows to reuse the results of (expensive) RAT candidate searches in certain cases.
These optimizations alone make our generator more than two times faster than drat-trim. 
% (Which performs less work, as it does not generate an enriched certificate)

Another common optimization is parallelization: If one has more DRAT certificates to check than processors 
available (\eg when evaluating a SAT competition), one can simply run multiple instances of the certificate generator and checker in parallel.
However, if one has only a few certificates to check (\eg when using SAT solvers for checking a single model), a more fine grained parallelization 
is required to keep the available processors busy. 
To this end, our certificate generator provides a multi-threaded mode,
which parallelizes the processing of lemmas, at the cost of using more memory. 
It uses all optimizations of the single-threaded mode, some of them slightly adjusted for multi-threading. 
For example, the lemmas of a run with the same pivot element are preferably scheduled to the same thread.

The basic idea is to let multiple threads run backwards over the certificate, verifying the lemmas in parallel.
A thread tries to acquire a lemma before it starts verification. If the lemma is already acquired 
by another thread, this thread proceeds with the next lemma. This way, each lemma is only proved by one thread.
For the marking of lemmas, the only required synchronization is that a thread sees its own markings: As every thread runs to the beginning, 
and on processing a lemma only earlier lemmas are marked, every thread will try to acquire at least the lemmas that it marked itself --- and 
process them if no other thread was faster. 
However, in order to improve the effectiveness of core-first unit propagation, the threads periodically synchronize on their marking data.

\section{Benchmarks}\label{sec:benchmarks}
We present the experimental evaluation of our tools on a realistic set of benchmarks.
We used CryptoMiniSat~\cite{SNC09,SATCOMP16} to generate DRAT certificates for the 110 unsatisfiable problems it solved at the 2016 SAT competition~\cite{satcomp-2016}.
% From these, we eliminated the 6 problems for which drat-trim could not check the certificate.
We ran the benchmarks on a standard server board with a 22 core Intel XEON Broadwell processor with 2.2 GHz and 128 GiB of RAM.
To minimize interferences, we ran only one benchmark at a time, with no other load on the server. 
Due to the page limit of this paper, we only provide a short summary of our benchmark results. The complete results are available on the tool's homepage~\cite{GRAT-homepage}.

On each DRAT certificate, we ran drat-trim (version Nov 10 2016)\footnote{The current version at the time of writing this paper.}
and our tool chain (version 1.2) with 1 and 8 threads.
We measured the wall-clock time and memory consumption. First of all, our tools successfully checked all certificates, 
indicating that our approach is sufficiently complete. (Recall that only soundness is formally proved)

We start with comparing drat-trim to our tool in single-threaded mode: drat-trim timed out after 20.000 seconds on two certificates, and crashed on a third one.
For checking the remaining 107 certificates, drat-trim required 42.3 hours, while our tool chain required only 17.3 hours.
% for both generating and checking the enriched certificates.
% That is, we get results with strong formal correctness guarantees in less than half the time. 
Out of the 17.3 hours, only 1.1 hours were required to run the verified certificate checker, \ie its runtime is almost negligible compared to certificate generation time.
Our tool-chain verified the three certificates for which drat-trim failed in 5.3 hours.

Our certificate generator requires roughly two times more memory than drat-trim. This is due to the generated certificate being stored 
in memory. We could not measure meaningful memory consumption values for our verified checker: The MLton garbage collector only gets active when memory falls short, 
resulting in unrealistic memory consumption values when being the only process running on a machine with 128 GiB of RAM.

Next, we report on running the certificate generator with 8 threads: The wall clock times required for generation and checking add up to only 8.3 hours.
Excluding certificates that required less than one minute to check, the average speed up is 2.6 [min: 1.1, max: 4.9] compared to single-threaded mode, 
and 7.1 [min: 0.5, max: 36.0] compared to drat-trim.
However, certificate generation requires significantly more memory, as the DRAT certificate is duplicated for each thread.
 
% We restrict the comparison to the 31 \emph{hard} problems, for which drat-trim required more than 1000 seconds.
% On average, our complete tool chain gets 2.5(4.1) times faster when using 4(12) threads for the generator, the minimum speedup is 1.6(2.2), the maximum is 3.4(7.3).
% Compared to drat-trim, we achieve an average speedup of 2.7(4.5) [min: 1.0(1.2); max: 4.8(8.8)], requiring an average of 4.6(11.2) times more memory [min: 3.3(8.3); max: 8.0(14.0)].
% The additional memory consumption results from the DRAT certificate being duplicated for each thread.

% Finally, we compare the enriched certificate size to the original certificate size: On average, the enriched certificate is 1.3 times larger than the original certificate. This only moderate increase in certificate size is mainly because many lemmas (72\% on average) turn out to be unnecessary during 
% certificate generation, and thus are not included into the certificate. The number of threads used to generate the certificate has no significant effect on certificate size.

To complete the presentation, we briefly report on the results of our formally verified satisfiability checker:
The certificates for the 64 satisfiable problems that CryptoMiniSat solved at the 2016 SAT competition~\cite{satcomp-2016} have a size of 229 MiB and could be verified in 40 seconds.

\section{Conclusions}\label{sec:concl}
We have presented a formally verified tool chain to check DRAT unsatisfiability certificates. 
In single-threaded mode, our approach is more than two times faster than the (unverified) standard tool drat-trim, on a benchmark 
suite taken from the 2016 SAT competition. Additionally, we implemented a multi-threaded mode, 
which allows us to trade computing resources for significantly smaller response times.
The formal proof covers the actual implementation of the checker and the semantics of the 
formula down to the sequence of integers that represents it.

Our approach involves two phases: The first phase generates an enriched certificate, 
which is then checked against the original formula by the second phase.
While the main computational work is done by the first phase, soundness of the approach 
only depends on the second phase, which is also algorithmically less complex, making it more amenable to formal verification. 
Using stepwise refinement techniques, we were able to formally verify a rather efficient implementation of the second phase.

We conclude with some statistics: The formalization of the certificate checker is roughly 5k lines of code.
In order to realize this formalization, several general purpose libraries (\eg the exception monad and some imperative data structures) had to be developed. 
These sum up to additional 3.5k lines. The time spent on the formalization was roughly three man months. The multi-threaded certificate generator has roughly 3k 
lines of code, and took two man month to develop.

\subsection{Future Work}
Currently, the formal proof of our verified checker goes down to the representation of the formula as integer array,
thus requiring a (small) unverified parser. A logical next step would be to verify the parser, too.
Moreover, verification stops at the Isabelle code generator, whose correctness is only proved the classical way on paper~\cite{HaNi10,HKKN13}. 
There is work aiming at the mechanical verification of code generators~\cite{MO14}, and even the subsequent compilers~\cite{KMNO14}. 
Unfortunately, this is not (yet) available for Isabelle/HOL. 

We plan to attack the high memory consumption of our multi-threaded generator by trying to share more (read-only) data between the threads.

An interesting research topic would be to integrate enriched certificate generation directly into SAT solvers. 
The performance decrease in the solver could be weighed against the cost of generating an enriched certificate.
However, such modifications are probably complex and SAT-solver specific, whereas DRAT certificates are designed to be 
easily integrated into virtually any CDCL based SAT solver.

Finally, we chose a benchmark set which is realistic, but can be run in a few days on the available hardware.
We plan to run our tools on larger benchmark suites, once we have access to sufficient (supercomputing) hardware.

\paragraph{Acknowledgements} We thank Jasmin Blanchette and Mathias Fleury for very useful comments on the draft version of this paper, 
and Lars Hupel for instant help on any problems related to the benchmark server.

\clearpage

\bibliographystyle{abbrv}
\bibliography{root}

\end{document}

