Package: drup
A Verified DRUP Proof CheckerAs the title suggests, a verified implementation of a checker for propositional unsatisfiability proofs in theDRUP formatthat is produced by many solvers.
The core of the checker is written inWhy3, which is extracted to OCaml, compiled natively, and exported as a C library with Python bindings.The checker also supports RAT clauses, so DRAT proofs are accepted.The current implementation is not optimized, and will be considerably slower thanDRAT-trimon large proofs (seeperformancebelow).Accordingly, the frontend does not accept proofs in binary format.InstallationIf you use a recent Linux distribution on x86_64, you should be able to install the compiled wheel from PyPI:$pipinstalldrupOtherwise, you need to have OCaml (>= 4.12), Ctypes (>=0.20), Why3 (>= 1.5.1), and Dune (>=2.9.3) installed.
The most straightforward way to install these is to useopam, which is available in most package systems, and then install Why3 and Dune (a sufficiently recent version of OCaml should already be installed with Opam):$opaminstallwhy3duneIf you do not intend to check the verification of the library or develop it further, then you do not need to install Why3's IDE or any of the solvers that it supports.Once OCaml and Why3 are installed, make sure that Pythonbuildis installed:$pipinstallbuildThen, clone this repository, build, and install the package:$gitclonehttps://github.com/cmu-transparency/verified_rup.git
$cdverified_rup
$python-mbuild
$pipinstalldist/*.whlUsageCommand line interfaceThe package provides a command line interface for checking proofs stored in files:$drup--help
usage:drup[-h][-d][-v]dimacsdrup

ChecksDRUP&DRATproofsagainstDIMACSsource.Returns0iftheproofisvalid,-1ifnot,oranegativeerrorcodeiftheinputisinvalid.

positionalarguments:dimacsPathtoaDIMACSCNFformuladrupPathtoaDRUP/DRATproof

optionalarguments:-h,--helpshowthishelpmessageandexit-d,--derivationCheckeachstep,ignoreabsenceofemptyclause-v,--verbosePrintdetailedinformationaboutfailedchecks

Formoreinformationvisithttps://github.com/cmu-transparency/verified_rupAs a Python moduleSee thedocumentationfor details of the API.
The primary function isdrup.check_proof, or alternatively,drup.check_derivationto check each step of the proof, ignoring the absence of an empty clause). There are corresponding convenience functionscheck_proof_from_stringsandcheck_proof_from_files, similarly forcheck_derivation.The following example usesCNFgento generate a PHP instance,
andPySATto solve it and generate a DRUP proof.
To illustrate the verbose output given for failed checks, only the first ten clauses of the proof are checked against the proof.importdrupimportcnfgenfrompysat.formulaimportCNFfrompysat.solversimportSolverdimacs=cnfgen.BinaryPigeonholePrinciple(4,3).dimacs()cnf=CNF(from_string=dimacs).clausesg4=Solver(name='g4',with_proof=True,bootstrap_with=cnf)g4.solve()proof=[[int(l)forlinc.split(' ')[:-1]]forcing4.get_proof()]drup.check_proof(cnf[:10],proof,verbose=True)This gives aCheckerResultobject with the following information:CheckerResult(Outcome.INVALID,[],RupInfo([4,6,7,8],[-4,-6,-7,-8,3,5]),RatInfo([4,6,7,8],[-4,-3],RupInfo([6,7,8,-3],[-6,-7,-8,5,3,-4])))TheRupInfocomponent relates that RUP verification failed on the first clause of the proof,4 6 7 8 0, after propagating the literals-4, -6, -7, -8, 3, 5, and failing to find more opposites to propagate.
Likewise, theRatInfocomponent relates that RAT verification on this clause failed when checking the pivot on clause-4 -3 0.
The resolvent of these clauses,6 7 8 -3 0, failed RUP verification after propagating-6 -7 -8 5 3 -4.PerformanceAt present, the implementation of RUP checking is not optimized, and drop lines are ignored.
Unit propagation does not take advantage of watched literals, and does not use mutable data structures.
Nonetheless, the performance compares well to that ofDRAT-trimon small proofs (<200 variables, a few hundred clauses).We measure this on random unsatisfiable instances generated by the procedure described in[1].
To evaluate the performance of DRAT-trim without the overhead of creating and tearing down a new process for each instance, we compiled it into a library with the samecheck_from_stringsinterface as the C library, and called it usingctypes.
In the table below, each configuration is run on 10,000 instances, with proofs generated byGlucose 4.# vars# clauses (avg)pf len (avg)drup (sec, avg)drat-trim (sec, avg)25147.77.30.0010.08550280.514.20.0060.17975413.526.30.0220.217100548.240.60.0680.172150811.8102.70.4070.3262001079.5227.91.9160.292References[1]Daniel Selsam, Matthew Lamm, Benedikt Bünz, Percy Liang, Leonardo de Moura, David L. Dill.Learning a SAT Solver from Single-Bit Supervision. International Conference on Learning Representations (ICLR), 2019.VerificationThe verification can be examined by runningsrc/librupchecker/rup_pure.mlwin Why3, or by checking the Why3 session insrc/librupchecker/rup_pure/why3session.xml.
The proof was developed using Why3 1.5.1, Alt-Ergo 2.4.0, Z3 4.8.6, and CVC4 1.8.
Verification has not been attempted with earlier versions of Why3 or the provers.The primary contract on the proof checker is as follows:requires{no_redundant_clausescnf/\no_trivial_clausescnf}requires{no_redundant_clausespf/\no_trivial_clausespf}ensures{matchresultwith|Valid->valid_proofcnfpf|_->proof_failureorigresultend}The bindings used by this library take care of removing redundant and trivial clauses.
Thevalid_proofpredicate is a straightforward translation of DRAT certification requirements.
Aproof_failureresult provides additional assurances about the verbose output of the checker.predicateproof_failure(cnf:cnf)(result:result)=matchresultwith|Valid->false|InvalidEmptystepsrup_info->rup_failure(steps++cnf)rup_info|InvalidStepstepsrup_inforat_info->rup_failure(steps++cnf)rup_info/\rat_failure(steps++cnf)rat_infoendAnInvalidEmptyresult indicates all of the listedstepsare valid, but the empty clause was not derived, as witnessed by the providedrup_info.
This is only returned when all of the steps in the proof are valid except an empty clause at the end.
Therup_infocomponent ensures that the empty clause is not RUP, and that the unit chain used to conclude this is exhaustive.predicaterup_failure(cnf:cnf)(info:rup_info)=not(rupcnfinfo.rup_clause)/\matchinfo.rup_clausewith|Nil->letcnf'=bcpcnfinfo.chaininis_unit_chaincnfinfo.chain/\forallchain'.is_unit_chaincnf'chain'->forallc.memc(bcpcnf'chain')<->memccnf'|Cons__->letcnf'=bcp(cnf++(negate_clauseinfo.rup_clause))info.chaininis_unit_chain(cnf++(negate_clauseinfo.rup_clause))info.chain/\forallchain'.is_unit_chaincnf'chain'->forallc.memc(bcpcnf'chain')<->memccnf'endAnInvalidStepresult indicates that thestepsare valid up to some non-empty step.
The next step in the certificate followingstepsis invalid, as witnessed by the providedrup_infoandrat_info.
In addition to the assurances onrup_infodescribed above,rat_infoprovides that the identified pivot clause is not RUP.predicaterat_failure(cnf:cnf)(info:rat_info)=not(ratcnfinfo.rat_clause)/\matchinfo.rat_clausewith|Nil->false|Consl_->meminfo.pivot_clause(pivot_clausescnfl)/\info.pivot_info.rup_clause=resolveinfo.rat_clauseinfo.pivot_clausel/\rup_failurecnfinfo.pivot_infoendThe derivation checker provides a similar contract, but rather than ensuringvalid_proofon success, it providesvalid_derivation.predicatevalid_derivation(cnf:cnf)(pf:proof)=matchpfwith|Nil->true|Consccs->(rupcnfc\/ratcnfc)/\valid_derivation(Consccnf)csendThis is the same condition asvalid_proof, but in theNilcase, the checker does not require that the empty clause is not RUP.AcknowledgementsMany thanks toFrank Pfenning,Joseph Reeves, andMarijn Huelefor the ongoing insightful discussions that led to this project.