tamarin-prover 0.8.1.0 → 0.8.2.0
raw patch · 64 files changed
+2537/−11349 lines, 64 filesdep +conduitdep +tamarin-prover-theorydep −fast-loggerdep −wai-loggerdep ~blaze-htmldep ~bytestringdep ~hamlet
Dependencies added: conduit, tamarin-prover-theory
Dependencies removed: fast-logger, wai-logger
Dependency ranges changed: blaze-html, bytestring, hamlet, http-types, tamarin-prover-term, tamarin-prover-utils, wai, warp, yesod-core, yesod-json, yesod-static
Files
- .ghci +1/−0
- data/AUTHORS +2/−1
- data/CHANGES +38/−0
- data/doc/MANUAL +16/−112
- data/examples/Tutorial.spthy +35/−11
- data/examples/classic/NSLPK3.spthy +23/−1
- data/examples/classic/NSPK3.spthy +177/−0
- data/examples/classic/TLS_Handshake.spthy +31/−9
- data/examples/csf12/Artificial.spthy +16/−13
- data/examples/loops/Minimal_HashChain.spthy +123/−0
- data/examples/loops/TESLA_Scheme1.spthy +17/−15
- data/examples/loops/TESLA_Scheme2.spthy +216/−0
- data/examples/loops/TESLA_Scheme2_lossless.spthy +206/−0
- data/examples/related_work/AIF_Moedersheim_CCS10/Keyserver.spthy +144/−74
- data/examples/related_work/StatVerif_ARR_CSF11/StatVerif_Example1.spthy +0/−149
- data/examples/related_work/StatVerif_ARR_CSF11/StatVerif_GM_Contract_Signing.spthy +305/−0
- data/examples/related_work/StatVerif_ARR_CSF11/StatVerif_Security_Device.spthy +149/−0
- data/examples/related_work/TPM_DKRS_CSF11/Envelope.spthy +150/−34
- data/examples/related_work/TPM_DKRS_CSF11/RunningExample.spthy +0/−123
- data/examples/related_work/TPM_DKRS_CSF11/TPM_Exclusive_Secrets.spthy +167/−0
- data/examples/related_work/YubiSecure_KS_STM12/Yubikey.spthy +227/−0
- data/examples/related_work/YubiSecure_KS_STM12/Yubikey_and_YubiHSM.spthy +277/−0
- interactive-only-src/Paths_tamarin_prover.hs +1/−1
- src/Main/Mode/Intruder.hs +1/−1
- src/Main/Mode/Test.hs +1/−1
- src/Main/TheoryLoader.hs +50/−6
- src/Test/ParserTests.hs +92/−0
- src/Theory.hs +0/−942
- src/Theory/Constraint/Solver.hs +0/−79
- src/Theory/Constraint/Solver/CaseDistinctions.hs +0/−318
- src/Theory/Constraint/Solver/Contradictions.hs +0/−242
- src/Theory/Constraint/Solver/Goals.hs +0/−284
- src/Theory/Constraint/Solver/ProofMethod.hs +0/−414
- src/Theory/Constraint/Solver/Reduction.hs +0/−665
- src/Theory/Constraint/Solver/Simplify.hs +0/−456
- src/Theory/Constraint/Solver/Types.hs +0/−150
- src/Theory/Constraint/System.hs +0/−482
- src/Theory/Constraint/System/Constraints.hs +0/−211
- src/Theory/Constraint/System/Dot.hs +0/−519
- src/Theory/Constraint/System/Guarded.hs +0/−650
- src/Theory/Model.hs +0/−25
- src/Theory/Model/Atom.hs +0/−156
- src/Theory/Model/Fact.hs +0/−353
- src/Theory/Model/Formula.hs +0/−324
- src/Theory/Model/Rule.hs +0/−632
- src/Theory/Model/Signature.hs +0/−172
- src/Theory/Proof.hs +0/−654
- src/Theory/Text/Parser.hs +0/−668
- src/Theory/Text/Parser/Token.hs +0/−398
- src/Theory/Text/Parser/UnitTests.hs +0/−91
- src/Theory/Text/Pretty.hs +0/−130
- src/Theory/Tools/AbstractInterpretation.hs +0/−147
- src/Theory/Tools/EquationStore.hs +0/−566
- src/Theory/Tools/InjectiveFactInstances.hs +0/−71
- src/Theory/Tools/IntruderRules.hs +0/−205
- src/Theory/Tools/LoopBreakers.hs +0/−80
- src/Theory/Tools/RuleVariants.hs +0/−100
- src/Theory/Tools/Wellformedness.hs +0/−519
- src/Web/Dispatch.hs +0/−7
- src/Web/Hamlet.hs +15/−3
- src/Web/Handler.hs +20/−15
- src/Web/Theory.hs +7/−3
- src/Web/Types.hs +3/−12
- tamarin-prover.cabal +27/−55
.ghci view
@@ -1,5 +1,6 @@ :set -iinteractive-only-src -- :set -ilib/term/src -- :set -ilib/utils/src+:set -ilib/theory/src :set -isrc :set -Wall -fwarn-tabs
data/AUTHORS view
@@ -1,7 +1,8 @@ Authors: Benedikt Schmidt <benedikt.schmidt@inf.ethz.ch>- Simon Meier <simon.meier@inf.ethz.ch>+ Simon Meier <iridcode@gmail.com> Contributors: protocol models, GUI: Cas Cremers <cas.cremers@inf.ethz.ch> original web interface: Cedric Staub <cs@cssx.ch+ YubiKey models: Robert Kuennemann <kunneman@lsv.ens-cachan.fr>
data/CHANGES view
@@ -1,3 +1,41 @@+* 0.8.2.0+ documentation:+ - The submitted draft of the Meier's PhD thesis on "Advancing Automated+ Security Protocol Analysis" is now available online at++ http://www.infsec.ethz.ch/research/software/tamarin++ It explains the theory underlying Tamarin in much more detail than our+ CSF'12 paper. It also explains the theory underlying trace induction+ and type assertions.++ user interface:+ - allow lemma selection with '--prove': The lemmas being analyzed are the+ ones whose name is an extension of one of the prefixes given with a+ '--prove' flag.+ - disallow parsing of reserved rule names:+ Fresh, irecv, isend, coerce, fresh, pub++ new protocol models (referenced in Meier's PhD thesis):+ - models of TESLA Scheme 1 and 2+ - modeled the+ - injective agreement for TLS and NSLPK++ - include the contributed YubiKey models from:+ "R. Kuennemann and G. Steel. Yubisecure? formal security analysis+ results for the Yubikey and YubiHSM. In Proc. of the 8th Workshop on+ Security and Trust Management (STM 2012), Pisa, Italy, September 2012."++ - minimal hash chain example: this demonstrates a short-coming in our+ current proof calculus. It does not suffice to reason about iterated+ function application.++ architectural changes:+ - upgraded the GUI to use version 1.1 of the Yesod web-framework+ - split off Theory module hierarchy as a separate library called+ 'tamarin-prover-theory'++ * 0.8.1.0 - enabled parallelization by default when compiling `tamarin-prover` with GHC 7.4 and higher. It uses as many threads as there are CPU cores on
data/doc/MANUAL view
@@ -1,7 +1,7 @@ User manual for the Tamarin prover ================================== -Date: 2012/06/04+Date: 2012/09/28 Authors: Simon Meier <iridcode@gmail.com>, Benedikt Schmidt <beschmi@gmail.com> @@ -140,122 +140,26 @@ Additional Theory ================= -Most of the theory behind the Tamarin prover is described in our CSF 2012-paper, whose extended version is available from+Most of the theory underlying the Tamarin prover is described in the submitted+draft of Meier's PhD thesis available from http://www.infsec.ethz.ch/research/software/tamarin -The implementation exploits a slightly more restricted definition of normal-dependency graphs and adapted versions of the constraint solving rules that-also allow security properties to refer to the conclusions of normal-construction rules. A technical report documenting this version of the-constraint solver is under preparation. From a usage perspective, the changes-are minor and explained below in the sections on-`Induction` and `Typing Invariants over the Extended Traces`.--Moreover, we added a constraint solving rule that allows to reason about-protocols that make use of exclusive access to linear facts. A typical example-is 'loops/Minimal_Create_Use_Destroy.spthy'. The corresponding constraint-reduction rule is explained below.--Apart from the above changes to the constraint solving rules, we also refined-the theory in two ways that allow to share work between different constraint-reduction steps. First, we store multiple constraint reduction steps in the-form of *precomputed case distinctions*. Second, we delay the enumeration of-the finite variants of multiset rewriting rules using an *equation store*. We-explain both of these refinements below.---Precomputed Case Distinctions--------------------------------Apart from unification, the most common step performed by Tamarin is the-enumeration of the possible origins of an open premise. Most of these-backwards steps result in a number of trivial further constraint reduction-steps being applied immediately. Instead of applying them over and over during-proof/counter-example construction, we precompute the result of doing one-backwards step and use the resulting precomputed case distinctions during-proof/counter-example search.--This precomputation is sound because the applicability of all our constraint-reduction rules is invariant under set union and instantiation. We precompute-cases for an arbitrary instance of every protocol fact and every outermost-constructor of a message.---Inductive Strengthening--------------------------The normal form conditions that we impose on dependency graphs can be seen as-a strong invariant on security protocol execution. As we have shown in our-case studies many security properties follow from these normal form-conditions. For some protocols, we must however strengthen security properties-before being able to prove them using our backwards reasoning technique. This-strengthening works by transforming the security property according to the-induction scheme associated with the set of traces of a protocol. Intuitively,-this strengthening amounts to searching for traces that violate the security-property, but do not contain any prefix that violates the security property.-Stated differently, we focus on first violations of security properties with-respect to the prefix-order on traces.--Properties that should be proven using induction can be marked with the-attribute [use_induction]. In the interactive GUI, one can just select-`induction` as a proof method provided the constraint system contains just one-guarded trace property.--For examples of protocols where inductive strengthening is required for a-successful proof, see the directories `examples/loops` and-`examples/related_work`.---Typing Invariants over the Extended Traces---------------------------------------------Note that every protocol communicating via the public network/adversary-implicitly contains loops. The adversary may send messages received from a-later step of one instance a role to an earlier step of another instance of-the same role. These loops manifest themselves during backwards reasoning as-infinite proof branches. For trivial loops where all messages are also-received as plain-text, we can prune these branches using the constraint-reduction rule N6. To prune more complicated loops, e.g., loops stemming from-receiving an encrypted message and sending out some of its contents, we need-so called typing invariants.--A typing invariant specifies the possible instantiations of a message variable-sent to the adversary. We describe these instantiations by relating an action-logging the instantiation in the rule sending the variable to actions logging-the possible instantiations in the rules sending the contents of this-variable. See the 'classic/NSLPK3.spthy' file for an example of a typing-invariant.--To enable the specification of the case that the intruder constructed the-message that a variable is instantiated with, we changed every construction-rule such that a KU-action logs the rule's conclusion. Properties referring to-this KU-actions can only be evaluated over the traces of normal dependency-graphs of a protocol. We call these traces the 'extended traces of a protocol'.-Note that we cannot transfer the validity of properties over extended traces-to the validity of these properties over standard traces stemming from the-multiset rewriting semantics. However, we can use these properties over-extended traces as lemmas during the proof of a property over standard traces-using the lemma attribute [reuse] or [typing].--The goal of typing invariants is to ensure that all chain-constraints are-solved during the precomputation of the case distinctions. We use a two-step-process to achieve this. We first precompute the so-called *untyped case-distinctions* without the assumption of the validity of any typing invariant.-These untyped precomputed case are used during the proof of a typing-invariant. We then use the typing invariants to refine the untyped case-distinctions to typed case distinctions. They are used during the proofs of-properties other than typing invariants.+Some of the missing pieces will be described in Schmidt's PhD thesis. His+thesis explains the notion of an equation store and design of the normal form+message deduction rules used to reason about Diffie-Hellman explanation,+bilinear pairings, and multiset union. Note that this version of Tamarin does+not yet support bilinear pairings and multiset union. It does support+Diffie-Hellman exponentiation as described in our CSF'12 paper,+and it uses equation stores as explained below. -In the input file, all typing invariants are marked with the [typing]-attribute. In the GUI, you can inspect both the untyped and typed precomputed-case distinctions. A typing invariant achieves its goal, if the typed-precomputed case distinctions are marked with "all chains solved".+Our preliminary support for reasoning about protocols that make use of+exclusive access to linear facts is not yet described as part of a research+paper. It is explained in the following subsection. Reasoning about Exclusivity: Facts Symbols with Injective Instances------------------------------------------------------------+------------------------------------------------------------------- We say that a fact symbol 'f' has *injective instances* with respect to a multiset rewriting system 'R', if there is no reachable state of@@ -354,8 +258,8 @@ A security protocol theory specifies a signature, an equational theory, a security protocol, and several lemmas, which formalize security properties.-The paper explaining the theory behind Tamarin has been published at CSF 2012-and its extended version is available from+The formal definition of security protocol theories is given in Meier's thesis+available from http://www.infsec.ethz.ch/research/software/tamarin
data/examples/Tutorial.spthy view
@@ -3,15 +3,14 @@ ============================================================== Authors: Simon Meier, Benedikt Schmidt-Date: April 2012+Date: September 2012 Introduction ------------ -This user guide assumes that you have a copy of our CSF'12 paper on-"Automated Analysis of Diffie-Hellman Protocols and Advanced Security-Properties", whose extended version is available from+This user guide assumes that you have a copy of the submitted draft of Meier's+PhD thesis, which is available from http://www.infsec.ethz.ch/research/software/tamarin. The input files for the Tamarin prover have the extension .spthy, which is@@ -20,8 +19,8 @@ 1. the signature and equational theory to use for the message algebra, 2. the set of set of multiset rewriting rules modeling the protocol and the adversary capabilities, and- 3. the guarded trace properties whose validity we wish to check for this- set of multiset rewriting rules.+ 3. the guarded trace properties whose satisfiability or validity we wish to+ check for this set of multiset rewriting rules. We explain each of these parts where they occur in the following security protocol theory. Before we start, a few notes on the syntax.@@ -332,6 +331,31 @@ " /*+Note that we can also strengthen the authentication property to a version of+injective authentication. Our formulation is stronger than the standard+formulation of injective authentication, as it is based on uniqueness instead+of counting. For most protocols, that guarantee injective authentication one+can also prove such a uniqueness claim, as they agree on appropriate fresh+data.+*/++lemma Client_auth_injective:+ " /* For all session keys 'k' setup by clients with a server 'S' */+ ( All S k #i. SessKeyC(S, k) @ #i+ ==>+ /* there is a server that answered the request */+ ( (Ex #a. AnswerRequest(S, k) @ a+ /* and there is no other client that had the same request */+ & (All #j. SessKeyC(S, k) @ #j ==> #i = #j)+ )+ /* or the intruder performed a long-term key reveal on 'S'+ before the key was setup. */+ | (Ex #r. LtkReveal(S) @ r & r < i)+ )+ )+ "++/* You can verify them by calling tamarin-prover --prove Tutorial.spthy@@ -382,16 +406,16 @@ Conclusion ---------- -By now, you should have enough knowledge to understand the case studies from-our CSF'12 paper. Recall that you can find them in the directory listed at the-bottom of the help message, when calling 'tamarin-prover' without any+By now, you should have enough knowledge to understand the case studies+included with Tamarin. Recall that you can find them in the directory listed+at the bottom of the help message, when calling 'tamarin-prover' without any arguments. Note that Tamarin also outputs the path to the reference MANUAL specifying and explaining the grammar of security protocol theories and giving-some additional hints on additional theory exploited by Tamarin. If you have+some additional hints on additional theory exploited by Tamarin. If you have further questions, please do not hesitate to contact either Benedikt Schmidt benedikt.schmidt@inf.ethz.ch- Simon Meier simon.meier@inf.ethz.ch+ Simon Meier iridcode@gmail.com Cas Cremers cas.cremers@inf.ethz.ch
data/examples/classic/NSLPK3.spthy view
@@ -60,6 +60,7 @@ ] --[ IN_R_1_ni( ni, m1 ) , OUT_R_1( m2 )+ , Running(I, $R, <'init',ni,~nr>) ]-> [ Out( m2 ) , St_R_1($R, I, ni, ~nr)@@ -75,6 +76,9 @@ , !Pk(R, pkR) ] --[ IN_I_2_nr( nr, m2)+ , Commit (I, R, <'init',ni,nr>) // need to log identities explicitely to+ , Running(R, I, <'resp',ni,nr>) // specify that they must not be+ // compromised in the property. ]-> [ Out( m3 ) , Secret(I,R,nr)@@ -86,7 +90,8 @@ , !Ltk(R, ltkR) , In( aenc{'3', nr}pk(ltkR) ) ]- --[]->+ --[ Commit (R, I, <'resp',ni,nr>)+ ]-> [ Secret(R,I,nr) , Secret(R,I,ni) ]@@ -141,6 +146,23 @@ & not (Ex #r. RevLtk(B) @ r) )" +// Injective agreement from the perspective of both the initiator and the responder.+lemma injective_agree:+ " /* Whenever somebody commits to running a session, then*/+ All actor peer params #i.+ Commit(actor, peer, params) @ i+ ==>+ /* there is somebody running a session with the same parameters */+ (Ex #j. Running(actor, peer, params) @ j & j < i+ /* and there is no other commit on the same parameters */+ & not(Ex actor2 peer2 #i2.+ Commit(actor2, peer2, params) @ i2 & not(#i = #i2)+ )+ )+ /* or the adversary perform a long-term key reveal on actor or peer */+ | (Ex #r. RevLtk(actor) @ r)+ | (Ex #r. RevLtk(peer) @ r)+ " // Consistency check: ensure that secrets can be shared between honest agents. lemma session_key_setup_possible:
+ data/examples/classic/NSPK3.spthy view
@@ -0,0 +1,177 @@+theory NSLPK3+begin++builtins: asymmetric-encryption++/*+ Protocol: The classic three message version of the+ flawed Needham-Schroeder Public Key Protocol+ Modeler: Simon Meier+ Date: September 2012++ Source: Gavin Lowe. Breaking and fixing the Needham-Schroeder+ public-key protocol using FDR. In Tiziana Margaria and+ Bernhard Steffen, editors, TACAS, volume 1055 of Lecture Notes+ in Computer Science, pages 147–166. Springer, 1996.++ Status: working++ Note that we are using explicit global constants for discerning the+ different encryption instead of the implicit typing.+ */+++// Public key infrastructure+rule Register_pk:+ [ Fr(~ltkA) ]+ -->+ [ !Ltk($A, ~ltkA), !Pk($A, pk(~ltkA)), Out(pk(~ltkA)) ]++rule Reveal_ltk:+ [ !Ltk(A, ltkA) ] --[ RevLtk(A) ]-> [ Out(ltkA) ]+++/* We formalize the following protocol++ protocol NSPK3 {+ 1. I -> R: {'1',ni,I}pk(R)+ 2. I <- R: {'2',ni,nr}pk(I)+ 3. I -> R: {'3',nr}pk(R)+ }+*/++rule I_1:+ let m1 = aenc{'1', ~ni, $I}pkR+ in+ [ Fr(~ni)+ , !Pk($R, pkR)+ ]+ --[ OUT_I_1(m1)+ ]->+ [ Out( m1 )+ , St_I_1($I, $R, ~ni)+ ]++rule R_1:+ let m1 = aenc{'1', ni, I}pk(ltkR)+ m2 = aenc{'2', ni, ~nr}pkI+ in+ [ !Ltk($R, ltkR)+ , In( m1 )+ , !Pk(I, pkI)+ , Fr(~nr)+ ]+ --[ IN_R_1_ni( ni, m1 )+ , OUT_R_1( m2 )+ , Running(I, $R, <'init',ni,~nr>)+ ]->+ [ Out( m2 )+ , St_R_1($R, I, ni, ~nr)+ ]++rule I_2:+ let m2 = aenc{'2', ni, nr}pk(ltkI)+ m3 = aenc{'3', nr}pkR+ in+ [ St_I_1(I, R, ni)+ , !Ltk(I, ltkI)+ , In( m2 )+ , !Pk(R, pkR)+ ]+ --[ IN_I_2_nr( nr, m2)+ , Commit (I, R, <'init',ni,nr>) // need to log identities explicitely to+ , Running(R, I, <'resp',ni,nr>) // specify that they must not be+ // compromised in the property.+ ]->+ [ Out( m3 )+ , Secret(I,R,nr)+ , Secret(I,R,ni)+ ]++rule R_2:+ [ St_R_1(R, I, ni, nr)+ , !Ltk(R, ltkR)+ , In( aenc{'3', nr}pk(ltkR) )+ ]+ --[ Commit (R, I, <'resp',ni,nr>)+ ]->+ [ Secret(R,I,nr)+ , Secret(R,I,ni)+ ]++/* TODO: Also model session-key reveals and adapt security properties. */+rule Secrecy_claim:+ [ Secret(A, B, m) ] --[ Secret(A, B, m) ]-> []++++/* Note that we are using an untyped protocol model. For proofs, we therefore+require a protocol specific type invariant for proof construction. In+principle, such an invariant is not required for attack search, but does help+a lot.++See 'NSLPK3.spthy' for a detailed explanation of the construction of this+invariant.+*/+lemma types [typing]:+ " (All ni m1 #i.+ IN_R_1_ni( ni, m1) @ i+ ==>+ ( (Ex #j. KU(ni) @ j & j < i)+ | (Ex #j. OUT_I_1( m1 ) @ j)+ )+ )+ & (All nr m2 #i.+ IN_I_2_nr( nr, m2) @ i+ ==>+ ( (Ex #j. KU(nr) @ j & j < i)+ | (Ex #j. OUT_R_1( m2 ) @ j)+ )+ )+ "++// Nonce secrecy from the perspective of both the initiator and the responder.+lemma nonce_secrecy:+ " /* It cannot be that */+ not(+ Ex A B s #i.+ /* somebody claims to have setup a shared secret, */+ Secret(A, B, s) @ i+ /* but the adversary knows it */+ & (Ex #j. K(s) @ j)+ /* without having performed a long-term key reveal. */+ & not (Ex #r. RevLtk(A) @ r)+ & not (Ex #r. RevLtk(B) @ r)+ )"++// Injective agreement from the perspective of both the initiator and the responder.+lemma injective_agree:+ " /* Whenever somebody commits to running a session, then*/+ All actor peer params #i.+ Commit(actor, peer, params) @ i+ ==>+ /* there is somebody running a session with the same parameters */+ (Ex #j. Running(actor, peer, params) @ j & j < i+ /* and there is no other commit on the same parameters */+ & not(Ex actor2 peer2 #i2.+ Commit(actor2, peer2, params) @ i2 & not(#i = #i2)+ )+ )+ /* or the adversary perform a long-term key reveal on actor or peer */+ | (Ex #r. RevLtk(actor) @ r)+ | (Ex #r. RevLtk(peer) @ r)+ "++// Consistency check: ensure that secrets can be shared between honest agents.+lemma session_key_setup_possible:+ exists-trace+ " /* It is possible that */+ Ex A B s #i.+ /* somebody claims to have setup a shared secret, */+ Secret(A, B, s) @ i+ /* without the adversary having performed a long-term key reveal. */+ & not (Ex #r. RevLtk(A) @ r)+ & not (Ex #r. RevLtk(B) @ r)+ "++end
data/examples/classic/TLS_Handshake.spthy view
@@ -87,6 +87,7 @@ let MS = PRF(~pms, nc, ns) Ckey = h('clientKey', nc, ns, MS)+ Skey = h('serverKey', nc, ns, MS) in [ St_C_1(C, nc, sid, pc) , In(@@ -96,7 +97,8 @@ , !Pk(S, pkS) , !Ltk(C, ltkC) ]- --[]->+ --[ Running(S, C, <'server', MS, Skey, Ckey>)+ ]-> [ Out( < aenc{ '31', ~pms }pkS , sign{ '32', h('32', ns, S, ~pms) }ltkC@@ -125,6 +127,8 @@ /* Explicit equality check, enforced as part of the property. */ --[ Eq(verify(signature, <'32', h('32', ns, S, pms)>, pkC), true ) , SessionKeys( S, C, Skey, Ckey )+ , Running(C, S, <'client', MS, Skey, Ckey>)+ , Commit(S, C, <'server', MS, Skey, Ckey>) ]-> [ Out( senc{ '4', sid, MS, nc, pc, C, ns, ps, S}Skey@@ -140,23 +144,23 @@ [ St_C_2(S, C, sid, nc, pc, ns, ps, pms) , In( senc{ '4', sid, MS, nc, pc, C, ns, ps, S}Skey ) ]- --[ SessionKeys( S, C, Skey, Ckey ) ]->+ --[ Commit(C, S, <'client', MS, Skey, Ckey>)+ , SessionKeys( S, C, Skey, Ckey )+ ]-> [] /* TODO: Also model session-key reveals and adapt security properties. */ +axiom Eq_check_succeed: "All x y #i. Eq(x,y) @ i ==> x = y" + /* Session key secrecy from the perspective of both the server and the client * for both the key of the server and the key of the client. Note that this * lemma thus captures four security properties at once. */ lemma session_key_secrecy:- " /* If all equality checks succeeded */- (All x y #i. Eq(x,y) @ i ==> x = y)- ==>- /* then there is no attack */- (not(- /* It cannot be that */+ /* It cannot be that */+ "not( Ex S C keyS keyC #k. /* somebody claims to have setup session keys, */ SessionKeys(S, C, keyS, keyC) @ k@@ -167,7 +171,25 @@ /* without having performed a long-term key reveal. */ & not (Ex #r. RevLtk(S) @ r) & not (Ex #r. RevLtk(C) @ r)- ) )"+ )"++// Injective agreement from the perspective of both the initiator and the responder.+lemma injective_agree:+ " /* Whenever somebody commits to running a session, then*/+ All actor peer params #i.+ Commit(actor, peer, params) @ i+ ==>+ /* there is somebody running a session with the same parameters */+ (Ex #j. Running(actor, peer, params) @ j & j < i+ /* and there is no other commit on the same parameters */+ & not(Ex actor2 peer2 #i2.+ Commit(actor2, peer2, params) @ i2 & not(#i = #i2)+ )+ )+ /* or the adversary perform a long-term key reveal on actor or peer */+ | (Ex #r. RevLtk(actor) @ r)+ | (Ex #r. RevLtk(peer) @ r)+ " /* Consistency check: ensure that session-keys can be setup between honest * agents. */
data/examples/csf12/Artificial.spthy view
@@ -5,10 +5,11 @@ Protocol: Example Modeler: Simon Meier, Benedikt Schmidt Date: January 2012- + Status: working- - This is the artificial protocol from our CSF'12 paper, which we use to++ This is the example protocol P_{Ex2} in Simon Meier's PhD thesis.+ It is also the artificial protocol from our CSF'12 paper, which we use to illustrate constraint solving and characterization. Note that, for characerization, you have to call the tamarin-prover as follows. @@ -43,23 +44,25 @@ builtins: symmetric-encryption rule Step1:- [ Fr(~x), Fr(~k) ] - --> - [ St(~x, ~k), Out(senc{~x}~k), Key(~k) ]+ [ Fr(x), Fr(k) ] --> [ St(x, k), Out(senc(x,k)), Key(k) ] rule Step2:- [ St(x, k), In(<x,x>) ] - --[ Fin(x, k) ]-> - [ ]+ [ St(x, k), In(x) ] --[ Fin(x, k) ]-> [ ] rule Reveal_key:- [ Key(k) ]- --[ Rev(k) ]->- [ Out(k) ]+ [ Key(k) ] --[ Rev(k) ]-> [ Out(k) ] // We search for trace-existence, as we want to characterize the possible // traces satisfying the given formula. lemma Characterize_Fin:- exists-trace "Ex k S #i. Fin(S, k) @ i"+ exists-trace+ "Ex k S #i. Fin(S, k) @ i"++lemma Fin_unique:+ "All S k #i #j. Fin(S, k) @ i & Fin(S, k) @ j ==> #i = #j"++lemma Keys_must_be_revealed:+ "All k S #i. Fin(S, k) @ i ==> Ex #j. Rev(k) @ j & j < i"+ end
+ data/examples/loops/Minimal_HashChain.spthy view
@@ -0,0 +1,123 @@+theory Minimal_HashChain begin++/*+ Protocol: A minimal HashChain example (inspired by TESLA 2)+ Modeler: Simon Meier+ Date: August 2012++ Status: note yet working+ (requires multiset or repeated exponentiation reasoning)++ This models the key difficulty in the proof of the TESLA 2 protocol with+ re-authentication: the verification that the key checking process is+ sufficient to guarantee that the key is a key of the hash-chain.+*/++functions: f/1++// Chain setup phase+////////////////////++// Hash chain generation+rule Gen_Start:+ [ Fr(seed) ] --> [ Gen(seed, seed), Out(seed) ]++// The NextKey-facts are used by the sender rules to store the link between+// the keys in the chain.+rule Gen_Step:+ [ Gen(seed, chain) ]+ --[ ChainKey(chain)+ ]->+ [ Gen(seed, f(chain) ) ]++// At some point the sender decides to stop the hash-chain precomputation.+rule Gen_Stop:+ [ Gen(seed, kZero) ]+ --[ ChainKey(kZero) ]->+ [ !Final(kZero) ]++// Key checking+///////////////++// Start checking an arbitrary key. Use a loop-id to allow connecting+// different statements about the same loop.+rule Check0:+ [ In(kOrig)+ , Fr(loopId)+ ]+ --[ Start(loopId, kOrig)+ ]->+ [ Loop(loopId, kOrig, kOrig) ]++rule Check:+ [ Loop(loopId, k, kOrig) ]+ --[ Loop(loopId, k, kOrig) ]->+ [ Loop(loopId, f(k), kOrig) ]++rule Success:+ [ Loop(loopId, kZero, kOrig), !Final(kZero) ]+ --[ Success(loopId, kOrig)+ ]-> []+++// Provable: restricts the search space+lemma Loop_Start [use_induction, reuse]:+ "All lid k kOrig #i. Loop(lid, k, kOrig) @ i ==>+ Ex #j. Start(lid, kOrig) @ j & j < i"++// Provable: restricts the search space+lemma Loop_Success_ord [use_induction, reuse]:+ "All lid k kOrig1 kOrig2 #i #j.+ Loop(lid, k, kOrig1) @ i+ & Success(lid, kOrig2) @ j+ ==>+ ( i < j)+ "++// Provable: connects an arbitrary loop step with its start.+lemma Loop_charn [use_induction]:+ "All lid k kOrig #i. Loop(lid, k, kOrig) @ i ==>+ Ex #j. Loop(lid, kOrig, kOrig) @ j"++// Not yet provable: the problem is that we cannot express the relation+// between the keys on two different segments of the same loop.+// @BS: Do you have an idea on how we could use multisets to formulate a+// strong enough invariant?+lemma Loop_and_success [use_induction]:+ "All lid k kOrig1 kOrig2 #i #j.+ Loop(lid, k, kOrig1) @ i+ & Success(lid, kOrig2) @ j+ ==>+ (Ex #j. ChainKey(k) @ j)+ "++// The ultimate goal! A successful check implies that the starting key is a+// key of the chain.+lemma Success_charn:+ "All lid k #i. Success(lid, k) @ i ==>+ Ex #j. ChainKey(k) @ j"++++/* A try on building the required 'smaller' relation in an axiomatic fashion.+ This interacts too strongly with++ Does not really work! We need a better way to express this stuff.++rule Succ_to_Smaller:+ [ !Succ(x, y) ] --[ IsSmaller(x, y) ]-> [!Smaller(x, y)]++rule Smaller_Extend:+ [ !Succ(x, y), !Smaller(y, z) ]+ --[ IsSmaller(x, z) ]->+ [ !Smaller(x, z) ]++axiom force_succ_smaller:+ "All #t1 2 a b c. IsSucc(a,b)@t1+ ==> Ex #t2 . IsSmaller(a,b)@t2 "++axiom transitivity:+ "All #t1 #t2 a b c. IsSmaller(a,b)@t1 & IsSmaller(b,c)@t2+ ==> Ex #t3 . IsSmaller(a,c)@t3 "+*/+end
data/examples/loops/TESLA_Scheme1.spthy view
@@ -82,29 +82,30 @@ // the signature on this commitment. We use the receiver nonce to identify // receivers. rule Receiver0a:- [ Fr( ~rid ) ]+ [ Fr( ~nR ) ] -->- [ Out( < $R, $S, ~rid > )- , Receiver0b( ~rid, $R, $S ) ]+ [ Out( < $R, $S, ~nR > )+ , Receiver0b( ~nR, $R, $S ) ] rule Receiver0b:- [ Receiver0b ( rid, R, S )+ [ Receiver0b ( nR, R, S ) , !Pk( S, pkS) , In( <S, R, commit_k1, signature> )+ , Fr(~rid) // Fresh name used to identify this receiver thread ]- -->- [ Receiver0b_check( rid, S, commit_k1- , verify(signature, <commit_k1, rid>, pkS)) ]+ --[ Setup(~rid) ]->+ [ Receiver0b_check( ~rid, S, commit_k1+ , verify(signature, <commit_k1, nR>, pkS)) ] rule Receiver0b_check:- [ Receiver0b_check(rid, S, commit_k1, true) ]+ [ Receiver0b_check(nR, S, commit_k1, true), Fr(~rid) ] -->- [ Receiver1( rid, S, commit_k1 ) ]+ [ Receiver1( nR, S, commit_k1 ) ] // Authenticated broadcasting rule Send1:- let data1 = <'1', ~m1, f(~k2)>+ let data1 = <~m1, f(~k2)> in [ Sender1(S, ~k1) , Fr(~m1)@@ -117,7 +118,7 @@ ] rule Recv1:- let data1 = <'1', m1, commit_k2>+ let data1 = <m1, commit_k2> in [ Receiver1(rid, S, commit_k1) , In( <data1, mac1> )@@ -127,7 +128,7 @@ [ Receiver(rid, S, data1, mac1, commit_k1, commit_k2) ] rule SendN:- let data = <'N', ~m, f(~kNew), ~kOld>+ let data = <~m, f(~kNew), ~kOld> in [ Sender(S, ~kOld, ~k) , Fr(~m)@@ -141,7 +142,7 @@ ] rule RecvN:- let data = <'N', m, commit_kNew, kOld>+ let data = <m, commit_kNew, kOld> in [ In(< data, mac >) , Receiver(rid, S, dataOld, MAC{dataOld}kOld, f(kOld), commit_k)@@ -162,8 +163,9 @@ "(All rid S m #i. FromSender(rid, S, m) @ i ==> /* the server actually sent that data */ ( (Ex #j. Sent(S, m) @ j & j < i)- /* or the server's longterm key was compromised before the claim */- | (Ex #j. RevealLtk(S) @ j & j < i)+ /* or the server's longterm key was compromised before the receiver's+ setup was complete */+ | (Ex #s #j. Setup(rid) @ s & RevealLtk(S) @ j & j < s) /* or one of the receivers expiredness assumptions before the claim was not met. */ | (Ex commit #ne #e. AssumeCommitNotExpired(rid, commit) @ ne
+ data/examples/loops/TESLA_Scheme2.spthy view
@@ -0,0 +1,216 @@+theory TESLA_Scheme2 begin++/*+ Protocol: The TESLA protocol, scheme 2+ Modeler: Simon Meier+ Date: September 2012++ Status: not yet working++ (We have trouble reasoning about the authenticity check. More+ precisely, we cannot prove that a successful check implies that+ the key is a key of the hash-chain. See Minimal_HashChain.spthy+ for an example of the core problem.)++ Original descrption in [1]. This model is based on the following description+ from [2].++ Msg 0a. R -> S : nR+ Msg 0b. S -> R : {k0 , nR }SK (S )+ Msg 1. S -> R : m1 , MAC (k1 , m1 ).++ And for n > 1:+ Msg n. S -> R : Dn , MAC (kn , Dn ) where Dn = mn , kn-1 .++ One aim of this second version is to be able to tolerate an arbitrary number of+ packet losses, and to drop unauthenticated packets, yet continue to authenticate+ later packets.++ We verify that the use of cryptography is correct under the assumption that+ the security condition holds. We do not verify that the timing schedule+ works, as we do not have a notion of time. For a manual, but machine-checked+ verification of the Scheme 2 of the TESLA protocol with time see [3].+++ [1] Perrig, Adrian, Ran Canetti, Dawn Song, and Doug Tygar. "The TESLA+ Broadcast Authentication Protocol." In RSA Cryptobytes, Summer 2002.++ [2] Philippa J. Hopcroft, Gavin Lowe: Analysing a stream authentication+ protocol using model checking. Int. J. Inf. Sec. 3(1): 2-13 (2004)++ [3] David A. Basin, Srdjan Capkun, Patrick Schaller, Benedikt Schmidt:+ Formal Reasoning about Physical Properties of Security Protocols. ACM Trans.+ Inf. Syst. Secur. 14(2): 16 (2011)++*/++builtins: signing++functions: MAC/2, f/1++// PKI+//////++rule Generate_Keypair:+ [ Fr(~ltk) ]+ -->+ [ !Ltk($A, ~ltk), !Pk($A, pk(~ltk)), Out(pk(~ltk)) ]++// We assume an active adversary.+// rule Reveal_Ltk:+// [ !Ltk(A, ltk) ]+// --[ RevealLtk(A) ]->+// [ Out(ltk) ]+++// Chain setup phase+////////////////////++// Hash chain generation+rule Gen_Start:+ [ Fr(~seed) ] --> [ Gen(~seed, ~seed) ]++// The NextKey-facts are used by the sender rules to store the link between+// the keys in the chain.+rule Gen_Step:+ [ Gen(seed, chain) ]+ --[ ChainKey(f(chain))+ ]->+ [ Gen(seed, f(chain) ) , NextKey( f(chain) , chain ) ]++// At some point the sender decides to stop the hash-chain precomputation.+rule Gen_Stop:+ [ Gen(seed, kZero) ]+ --[ Expired(kZero), KeyZero(seed, kZero) ]->+ [ Sender(kZero) , !Sender0($S, kZero) ]++// Intial chain key distribution+////////////////////////////////++// Everybody can listen in by sending a request for k_0.+rule Sender0:+ let msgZero = <nR, kZero>+ in+ [ !Sender0(S, kZero), !Ltk(S, ltkS), In(nR) ]+ -->+ [ Out( <msgZero, sign{msgZero}ltkS> ) ]++// Receivers start by requesting key k_0 adn verifying the signature on this+// response.+rule Receiver0a:+ [ Fr(~nR) ] --> [ Receiver0b($S, ~nR), Out(<$S, ~nR>) ]++rule Receiver0b:+ let msgZero = <nR, kZero>+ in+ [ Receiver0b(S,nR), !Pk(S, pkS), In(<msgZero, signature>) ]+ --[ Eq( verify(signature, msgZero, pkS), true() ) ]->+ [ !Receiver(S, kZero) ]++// Sending+//////////++// We use the convention that k_{n-1} is denoted as kNp, where the 'p' stands+// for predecessor.+rule Sender:+ let msgN = <mN, MAC( kN, mN )>+ in+ [ Sender( kNp ), NextKey( kNp, kN), Fr(mN) ]+ --[ Expired( kNp )+ , Sent( msgN )+ ]->+ [ Sender( kN ), Out(kNp), Out(msgN) ]++// Receiving+////////////++rule Receiver:+ let msg = <m, mac>+ in+ [ !Receiver(S, kZero), In( msg )+ , Fr(expiryCheck)+ ]+ --[ NotExpiredHere( expiryCheck ) ]->+ [ CheckAuth(expiryCheck, S, kZero, msg ) ]++rule CheckAuth0:+ let args = <kZero, expiryCheck, S, k, msg>+ in+ [ CheckAuth(expiryCheck, S, kZero, msg)+ , In(k)+ , Fr(loopId)+ ]+ --[ CheckStart(loopId, args)+ ]->+ [ CheckAuthLoop(loopId, k, args) ]++rule CheckAuth:+ [ CheckAuthLoop(loopId, k, args) ]+ --[ CheckLoop( loopId, f(k), args )+ ]->+ [ CheckAuthLoop(loopId, f(k), args) ]+++rule CheckAuthClaim:+ let msg = <m, MAC(kRef,m)>+ in+ [ CheckAuthLoop(loopId, kZero, <kZero, expiryCheck, S, kRef, msg>) ]+ --[ FromSender(msg, S, kRef, expiryCheck)+ , Success(loopId)+ ]-> []+++// Axioms; i.e., universal restrictions on the traces of interest+/////////////////////////////////////////////////////////////////++axiom Eq_checks_succeed: "(All x y #j. Eq(x, y) @ j ==> x = y)"++axiom Neq_checks_succeed: "(All x #j. Neq(x, x) @ j ==> F)"++// The security condition of TESLA guarantees that+axiom Security_condition:+ "All m S k check #i #j #e.+ FromSender(m, S, k, check) @ i+ & NotExpiredHere(check) @ j+ & Expired(k) @ e+ ==>+ j < e"+++// Security properties+//////////////////////++// The following two lemmas constraint the search space strongly enough to+// allow reasoning about the authenticity of the received messages.++lemma chain_keys_unique [use_induction, reuse]:+ "All k #i #j. ChainKey(k) @ i & ChainKey(k) @ j ==> #i = #j"++lemma knows_only_expired_chain_keys [use_induction, reuse]:+ "All k #i #j. ChainKey(k) @i & KU(k) @ j ==>+ (Ex #e. Expired(k) @ e & e < j)"++// The current proof idea is to assume this axiom because we cannot yet prove+// it. Proving it requires support for multisets or repeated function+// application.+axiom FromSender_charn: // [use_induction]:+ // "All m S k0 lid k args check #i.+ "All lid k args #i #j.+ // FromSender(m, S, k0, check) @ i+ Success(lid) @ i+ & CheckLoop(lid, k, args) @ j+ ==>+ (Ex #c. ChainKey(k) @ c)+ "++lemma authentic [use_induction]:+ " (All m S k check #i.+ FromSender(m, S, k, check) @ i+ ==>+ (Ex #j. Sent(m) @ j & j < i)+ // | (Ex #j. RevealLtk(S) @ j & j < i)+ )+ "+++end
+ data/examples/loops/TESLA_Scheme2_lossless.spthy view
@@ -0,0 +1,206 @@+theory TESLA_Scheme2_lossless begin++/*+ Protocol: The TESLA protocol, scheme 2 (no re-authentication)+ Modeler: Simon Meier+ Date: September 2012++ Status: working++ Original descrption in [1]. This model is based on the following description+ from [2].++ Msg 0a. R -> S : nR+ Msg 0b. S -> R : {k0 , nR }SK (S )+ Msg 1. S -> R : m1 , MAC (k1 , m1 ).++ And for n > 1:+ Msg n. S -> R : Dn , MAC (kn , Dn ) where Dn = mn , kn-1 .++ Here, we model a simplified version of Scheme 2, which does not allow a+ receiver to re-authenticate once it missed a packet. We have not yet managed+ to verify a model that allows this re-authentication. See+ TESLA_Scheme2.spthy for more information on the problem.++ We verify that the use of cryptography is correct under the assumption that+ the security condition holds. We do not verify that the timing schedule+ works, as we do not have a notion of time. For a manual, but machine-checked+ verification of the Scheme 2 of the TESLA protocol with time see [3].+++ [1] Perrig, Adrian, Ran Canetti, Dawn Song, and Doug Tygar. "The TESLA+ Broadcast Authentication Protocol." In RSA Cryptobytes, Summer 2002.++ [2] Philippa J. Hopcroft, Gavin Lowe: Analysing a stream authentication+ protocol using model checking. Int. J. Inf. Sec. 3(1): 2-13 (2004)++ [3] David A. Basin, Srdjan Capkun, Patrick Schaller, Benedikt Schmidt:+ Formal Reasoning about Physical Properties of Security Protocols. ACM Trans.+ Inf. Syst. Secur. 14(2): 16 (2011)++*/++builtins: signing++functions: MAC/2, f/1++// PKI+//////++rule Generate_Keypair:+ [ Fr(~ltk) ]+ -->+ [ !Ltk($A, ~ltk), !Pk($A, pk(~ltk)), Out(pk(~ltk)) ]++// We assume an active adversary.+rule Reveal_Ltk:+ [ !Ltk(A, ltk) ]+ --[ RevealLtk(A) ]->+ [ Out(ltk) ]+++// Chain setup phase+////////////////////++// Hash chain generation+rule Gen_Start:+ [ Fr(~seed) ] --> [ Gen(~seed) ]++// The NextKey-facts are used by the sender rules to store the link between+// the keys in the chain.+rule Gen_Step:+ [ Gen(chain) ]+ --[ ChainKey(f(chain)) ]->+ [ Gen( f(chain) ) , NextKey( f(chain) , chain ) ]++// At some point the sender decides to stop the hash-chain precomputation.+rule Gen_Stop:+ [ Gen(kZero) ]+ --[ Expired(kZero) ]->+ [ Sender1( $S, kZero) , !Sender0($S, kZero) ]++// Intial chain key distribution+////////////////////////////////++// Everybody can listen in by sending a request for k_0.+rule Sender0:+ let msgZero = <nR, kZero>+ in+ [ !Sender0(S, kZero), !Ltk(S, ltkS), In(nR) ]+ -->+ [ Out( <msgZero, sign{msgZero}ltkS> ) ]++// Receivers start by requesting key k_0 adn verifying the signature on this+// response.+rule Receiver0a:+ [ Fr(~nR) ] --> [ Receiver0b($S, ~nR), Out(<$S, ~nR>) ]++rule Receiver0b:+ let msgZero = <nR, kZero>+ in+ [ Receiver0b(S,nR), !Pk(S, pkS), In(<msgZero, signature>) ]+ --[ Eq( verify(signature, msgZero, pkS), true() ) ]->+ [ Receiver1(S,kZero) ]++// Sending+//////////++rule Sender1:+ let msgOne = <mOne, MAC(kOne, mOne)>+ in+ [ Sender1( S, kZero ), NextKey( kZero, kOne ), Fr(mOne) ]+ --[ Sent(S, msgOne) ]->+ [ SenderN( S, kOne ), Out( msgOne ) ]++// We use the convention that k_{n-1} is denoted as kNp, where the 'p' stands+// for predecessor.+rule SenderN:+ let msgN = <kNp, mN, MAC( kN, mN )>+ in+ [ SenderN( S, kNp ), NextKey( kNp, kN), Fr(mN) ]+ --[ Expired( kNp )+ , Sent( S, msgN )+ ]->+ [ SenderN( S, kN ), Out(msgN) ]++// Receiving+////////////++rule Receiver1:+ let msgOne = <mOne, macOne>+ in+ [ Receiver1(S, kZero), In( msgOne ), Fr(expiryCheckOne) ]+ --[ NotExpiredHere(expiryCheckOne) ]->+ [ ReceiverN(S, kZero, expiryCheckOne, msgOne, mOne, macOne ) ]++rule ReceiverN:+ let msgN = <kNp, mN, macN>+ in+ [ ReceiverN(S, kNpp, expiryCheckNp, msgNp, mNp, macNp), In( msgN )+ , Fr(expiryCheckN)+ ]+ --[ Eq( kNpp, f(kNp) ), Eq( macNp, MAC(kNp, mNp) )+ // This action claims that 'msgNp' was sent by the sender provided that+ // the longterm-key of 'S' was not revealed before and the key 'kNp'+ // expired after the expiry check denoted by 'expiryCheckNp'.+ , FromSender(msgNp, S, kNp, expiryCheckNp)+ , NotExpiredHere( expiryCheckN )+ ]->+ [ ReceiverN(S, kNp, expiryCheckN, msgN, mN, macN ) ]++// Axioms; i.e., universal restrictions on the traces of interest+/////////////////////////////////////////////////////////////////++// We are only interested in traces where all equality checks succeed.+axiom Eq_checks_succeed: "(All x y #j. Eq(x, y) @ j ==> x = y)"++// The security condition of TESLA guarantees that a key never expires+// before the receiver considers it to be expired. This means that we assume+// that the clocks are synchronized. Then, the clock checks are sufficient to+// guarantee this property.+axiom Security_condition:+ "All m S k check #i #j #e.+ FromSender(m, S, k, check) @ i+ & NotExpiredHere(check) @ j+ & Expired(k) @ e+ ==>+ j < e"+++// Security properties+//////////////////////++// Sanity check: there is a honest execution where no key expired too early.+lemma honestly_executable:+ exists-trace+ " ( Ex m S k check #i #j.+ FromSender(m, S, k, check) @ i+ & Sent(S, m) @ j+ )+ & (All S #r. RevealLtk(S) @ r ==> F) // no longterm key was revealed+ "++// The following two lemmas constraint the search space strongly enough to+// allow reasoning about the authenticity of the received messages.++lemma chain_keys_unique [use_induction, reuse]:+ "All k #i #j. ChainKey(k) @ i & ChainKey(k) @ j ==> #i = #j"++lemma knows_only_expired_chain_keys [use_induction, reuse]:+ "All k #i #j. ChainKey(k) @i & KU(k) @ j ==>+ (Ex #e. Expired(k) @ e & e < j)"++// The desired security property:+lemma authentic [use_induction]:+ " (All m S k check #i.+ /* Whenever the reciever states that it received an authentic message, */+ FromSender(m, S, k, check) @ i+ ==>+ /* the sender sent it */+ (Ex #j. Sent(S, m) @ j & j < i)+ /* or the adversary revealed the longterm key of the sender. */+ | (Ex #j. RevealLtk(S) @ j & j < i)+ )+ "++end
@@ -7,115 +7,185 @@ * * Status: working + [1] Sebastian Moedersheim: Abstraction by set-membership: verifying security protocols and web services with databases. ACM Conference on Computer and Communications Security 2010: 351-360- */ -/* Original input file from [1]+ The original model from [1]. -Problem: zebsKeyserver;+ Problem: zebsKeyserver; -Types:-Agent : {a,b,c,i,s};-U : {a,b,c};-S : {s};-H : {a,b};-D : {c,i};-DU : {c};-Sts : {valid,revoked};-PK,NPK : value;-M1,M2 : untyped;+ Types:+ Agent : {a,b,c,i,s};+ U : {a,b,c};+ S : {s};+ H : {a,b};+ D : {c,i};+ DU : {c};+ Sts : {valid,revoked};+ PK,NPK : value;+ M1,M2 : untyped; -Sets:-ring(U), db(S,U,Sts);+ Sets:+ ring(U), db(S,U,Sts); -Functions:-public sign/2, pair/2;-private inv/1;+ Functions:+ public sign/2, pair/2;+ private inv/1; -Facts:-iknows/1, attack/0;+ Facts:+ iknows/1, attack/0; -Rules:+ Rules: -\Agent. => iknows(Agent);-iknows(sign(M1,M2)) => iknows(M2);-iknows(M1).iknows(M2) => iknows(sign(M1,M2));-iknows(pair(M1,M2)) => iknows(M1).iknows(M2);-iknows(M1).iknows(M2) => iknows(pair(M1,M2));+ \Agent. => iknows(Agent);+ iknows(sign(M1,M2)) => iknows(M2);+ iknows(M1).iknows(M2) => iknows(sign(M1,M2));+ iknows(pair(M1,M2)) => iknows(M1).iknows(M2);+ iknows(M1).iknows(M2) => iknows(pair(M1,M2));++ \H,S. =[PK]=>iknows(PK).PK in ring(H).PK in db(S,H,valid);++ \S,DU. =[PK]=>iknows(PK).iknows(inv(PK)).PK in db(S,DU,valid);++ \H.+ iknows(PK).PK in ring(H)+ =[NPK]=>NPK in ring(H).iknows(sign(inv(PK),pair(H,NPK)));++ \S,U.+ iknows(sign(inv(PK),pair(U,NPK))).PK in db(S,U,valid).+ forall U,Sts. NPK notin db(S,U,Sts)+ =>PK in db(S,U,revoked).NPK in db(S,U,valid).iknows(inv(PK));++ \S,H.+ iknows(inv(PK)).PK in db(S,H,valid)+ =>attack;++ Unfortunately, there are no comments. Moreover, public keys are converted+ freely to private keys, which is not always faithful. We comment on this+ below. */ builtins: signing -// The non-deterministic choice between the rules SetupHonestKey and-// SetupDishonestKey determines whether an agent is honest or not.+/* We also setup a server key to allow server signatures. */+rule SetupServerKey:+ [ Fr(~sk) ]+ -->+ [ !ServerSK(~sk), !ServerPK(pk(~sk)), Out(pk(~sk)) ] -// \H,S. =[PK]=>iknows(PK).PK in ring(H).PK in db(S,H,valid);+/*+ The non-deterministic choice between the rules SetupHonestKey and+ SetupDishonestKey determines whether an agent is honest or not.++ The rule below models++ \H,S. =[PK]=>iknows(PK).PK in ring(H).PK in db(S,H,valid);++ Note that servers store public keys and clients store their private key.+ There may be several registered keys at the same time, as there may be+ multiple ServerKey-facts in the state at the same time.+*/ rule SetupHonestKey: [ Fr(~sk) ] --[ HonestKey(~sk) ]->- [ Out(pk(~sk)), ClientKey($A, ~sk), ServerDB($A, ~sk) ]+ [ Out(pk(~sk)) , ClientKey($A, ~sk) , ServerDB($A, pk(~sk)) ] -// \S,DU. =[PK]=>iknows(PK).iknows(inv(PK)).PK in db(S,DU,valid);++/* The intruder may register any private key for any agent.++ \S,DU. =[PK]=>iknows(PK).iknows(inv(PK)).PK in db(S,DU,valid);++*/ rule SetupDishonestKey:- [ In(sk) ]- -->- [ ServerDB($A, sk) ]+ [ In(sk) ] --> [ ServerDB($A, pk(sk)) ] -// \H.-// iknows(PK).PK in ring(H)-// =[NPK]=>NPK in ring(H).iknows(sign(inv(PK),pair(H,NPK)));-rule RequestRenewKey:- [ ClientKey($A, sk), Fr(~skNew) ]+/* A client may renew one of his keys by sending a renew request. In [1], the+ server then leaks the corresponding private key. This is not really+ possible, as the server does not know the private keys corresponding to+ newly setup keys. We model that the key waits for a confirmation of his+ request and only then leaks his key++ The original client request rule was:++ \H.+ iknows(PK).PK in ring(H)+ =[NPK]=>NPK in ring(H).iknows(sign(inv(PK),pair(H,NPK)));+*/+rule Client_RenewKey:+ let pkNew = pk(~skNew)+ request = <'renew', $A, pkNew>+ requestSig = sign{request}~sk+ in+ [ ClientKey($A, ~sk), Fr(~skNew) ] --[ HonestKey(~skNew) ]->- [ Out( sign{'renew', $A, pk(~skNew)}sk )+ [ Out( <request, requestSig> ) , ClientKey($A, ~skNew)+ , AwaitConfirmation(requestSig,~sk) ] -// \S,U.-// iknows(sign(inv(PK),pair(U,NPK))).PK in db(S,U,valid).-// forall U,Sts. NPK notin db(S,U,Sts)-// =>PK in db(S,U,revoked).NPK in db(S,U,valid).iknows(inv(PK));-rule RenewKey:- [ In( sign{'renew', A, pk(skNew)}sk )- , ServerDB(A, sk)- ]- --[ Revoked(sk) ]->- [ ServerDB(A, skNew)- , Out( sk )+rule Client_LeakKey:+ [ AwaitConfirmation(request,sk)+ , !ServerPK(pkServer)+ , In(sig) ]+ --[ Eq(verify(sig, <'confirm', request>, pkServer), true)+ , Revoked(sk)+ ]->+ [ Out(sk) ] -// Typing lemma: it can be proven, but not with the current heuristic. It-// focuses too much on the first-order part and neglects solving the-// signature. Moreover, it should use an age-based strategy for the goals to-// ensure that it always makes at least some progress.-lemma types [typing]:- "All sk #i. Revoked(sk) @ i ==>- ( (Ex #j. KU(sk) @ j & j < i)- | (Ex #j. HonestKey(sk) @ j & j < i)- )- "-/*-The following property proven in Moedersheim's paper is rather easy to-prove, as it depends only on the fact that secret keys are not leaked by-any other means than the "RenewKey" rule. The "RenewKey" rule always log's-that the key is "Revoked", which directly implies the lemma below.+/* The server updating his database. See the comment above for the change in+ leaking the private key. The original rule in [1] is -TODO: Prove property that depends on ordering of revocation. For example,-DH-key exchange always succeeds for a protocol with an online key-server. This-crucially depends on the client not sending a renewal message while he's-waiting for the key confirmation.+ \S,U.+ iknows(sign(inv(PK),pair(U,NPK))).PK in db(S,U,valid).+ forall U,Sts. NPK notin db(S,U,Sts)+ =>PK in db(S,U,revoked).NPK in db(S,U,valid).iknows(inv(PK));++ The leaking of 'inv(PK)' is unrealistic as the server only learns the+ public key of new messages. */+rule Server_RenewKey:+ let request = <'renew', A, pkNew>+ in+ [ In( <request, requestSig> )+ , ServerDB(A, pk(sk))+ , !ServerSK(skServer)+ ]+ --[ Eq(verify(requestSig, request, pk(sk)), true)+ ]->+ [ ServerDB(A, pkNew)+ , Out(sign{'confirm', requestSig}skServer)+ ] +// We assume that rule's are only executed if their equality checks succeed.+axiom Eq_checks_succeed: "All x y #i. Eq(x,y) @ i ==> x = y" -// \S,H.-// iknows(inv(PK)).PK in db(S,H,valid)-// =>attack;-lemma In_Honest_Key_imp_Revoked:+/* The following property proven in Moedersheim's paper is rather easy to+ prove, as it depends only on the fact that secret keys are not leaked by+ any other means than the "RenewKey" rule. The "RenewKey" rule always log's+ that the key is "Revoked", which directly implies the lemma below.++ \S,H.+ iknows(inv(PK)).PK in db(S,H,valid)+ =>attack;+*/++lemma Knows_Honest_Key_imp_Revoked: "All sk #i #d. HonestKey(sk) @ i & K(sk) @ d ==> (Ex #r. Revoked(sk) @ r) " +/*+/* Sanity check. Commented out for runtime comparison to [1]. */+lemma Honest_Revoked_Known_Reachable:+ exists-trace+ "(Ex sk #i #j #r. HonestKey(sk) @ i+ & K(sk) @ j+ & Revoked(sk) @ r+ )"+*/ end+
@@ -1,149 +0,0 @@-theory StatVerif_Example1 begin--/*- Protocol: Simple security device (Example 1 from [1])- Modeler: Simon Meier- Date: May 2012-- Status: working-- This is the simple security device example presented in Section V.A of the- following paper.-- [1] M. Arapinis, E. Ritter and M. Ryan. StatVerif: Verification of Stateful- Processes. In CSF'11. IEEE Computer Society Press, pages 33-47 , 2011.-- It models a hardware device that stores both a private key and a- configuration register that can be set to 'left' for decrypting the first- component of tuples encrypted using the device's public key and 'right' for- decrypting the second component of tuples encrypted using the device's- public key. Alice uses such a device to encrypt tuples such that Bob- can access either all their first components or all their second- components, but never both.-- Note that in contrast to [1], we allow the creation of an unbounded number- of devices. We also verify both the accessibility of left and right- components, as well as their exclusivity. The source code of the model- from [1] is attached at the end of this file.--*/--builtins: asymmetric-encryption---// Create a new device. It stores the private key and publishes the-// corresponding public key.-rule NewDevice:- [ Fr(~sk) // We let the key identify the device.- ]- -->- [ UnconfiguredDevice(~sk)- , !DevicePublicKey(pk(~sk))- , Out(pk(~sk))- ]--// Alice can use any public key associated to such a hardware security device-// for publishing messages with exclusive access.-rule Alice:- [ Fr(~x)- , Fr(~y)- , !DevicePublicKey(key)- ]- --[ Exclusive(~x,~y) ]->- [ Out( aenc{~x,~y}key )- ]--// Unconfigured devices can be configured exactly once.-rule ConfigureDevice:- [ UnconfiguredDevice(sk), In(config) ]- -->- [ !ConfiguredDevice(sk, config) ]--// Devices configured to 'left' can be used to decrypt the first component of-// messages encrypted using the device's public key.-rule UseLeftDevice:- [ !ConfiguredDevice(sk, 'left'), In(aenc{x,y}pk(sk)) ]- --[ Access(x) ]->- [ Out(x) ]--// Devices configured to 'right' can be used to decrypt the second component of-// messages encrypted using the device's public key.-rule UseRightDevice:- [ !ConfiguredDevice(sk, 'right'), In(aenc{x,y}pk(sk)) ]- --[ Access(y) ]->- [ Out(y) ]---// As we use a backwards search, we must specify the possible structure of-// messages sent in 'UseLeftDevice' and 'UseRightDevice' precise enough such-// that we can solve all chain constraints starting from the sent message. We-// therefore log the message being accessed and relate it to its possible-// origins: known to the intruder in an earlier step or part of an exclusive-// message generated by 'Alice'. Typing lemmas are proven by induction and-// incorporated in the case distinction precomputation.-lemma types [typing]:- "All m #i. Access(m) @ i ==>- (Ex #j. KU(m) @ j & j < i) // Make use of the KU-facts logged- // by the construction rules.- | (Ex x #j. Exclusive(x,m) @ j)- | (Ex y #j. Exclusive(m,y) @ j)- "--// Check that there is some trace where the intruder knows the left message of-// an exclusive message-tuple. In contrast to the typing lemma, we use the-// standard 'K'-fact, which is logged by the built-in 'ISend' rule.-lemma reachability_left:- exists-trace- "Ex x y #i #j. Exclusive(x,y) @i & K(x) @ j"--lemma reachability_right:- exists-trace- "Ex x y #i #k. Exclusive(x,y) @i & K(y) @ k"--// Check that exclusivity is maintained-lemma secrecy:- "not(Ex x y #i #k1 #k2.- Exclusive(x,y) @i & K(x) @ k1 & K(y) @ k2- )- "--end--/* StatVerif source code of the original model from [1].--fun pair/2.-fun aenc/3.-fun pk/1.-free left.-free right.-free init.-free c.--reduc- projl(pair(xleft, xright)) = xleft;- projr(pair(xleft, xright)) = xright;- adec(u, aenc(pk(u), v, w)) = w.--query- att:vs,pair(sl,sr).--let device =- out(c, pk(k)) |- ( ! lock(s); in(c, x); read s as y;- if y = init then- (if x = left then s := x; unlock(s)- else if x = right then s := x; unlock(s)) ) |- ( ! lock(s); in(c, x); read s as y; let z = adec(k, x) in- let zl = projl(z) in- let zr = projr(z) in- ((if y = left then out(c, zl); unlock(s)) |- (if y = right then out(c, zr); unlock(s)))).--let user =- new sl; new sr; new r;- out(c, aenc(pk(k), r, pair(sl,sr))).--process- new k; new s; [s |-> init] | device | ! user--*/
@@ -0,0 +1,305 @@+theory StatVerif_GM_Contract_Signing begin++/*+ Protocol: Contract Signing Protocol (Example 2 from [1])+ Modeler: Simon Meier <iridcode@gmail.com>+ Date: September 2012++ Status: working++ This is the contract signing example presented in Section V.B of the+ following paper.++ [1] M. Arapinis, E. Ritter and M. Ryan. StatVerif: Verification of Stateful+ Processes. In CSF'11. IEEE Computer Society Press, pages 33-47 , 2011.++ It models the two-party version of the contract signing protocol proposed+ in++ [2] Juan A. Garay, Markus Jakobsson, and Philip D. MacKenzie. Abuse-free+ optimistic contract signing. In Michael J. Wiener, editor, CRYPTO, volume+ 1666 of Lecture Notes in Computer Science, pages 449–466. Springer, 1999.++ Note that in contrast to [1], we do not require any protocol-specific+ abstraction, as we support reasoning about state under replication.++*/++functions:+ pk/1, sign/2, pcs/3, check_getmsg/2, checkpcs/5, true/0, convertpcs/2++equations:+ // Checking and getting the message in a standard signature+ check_getmsg(pk(xsk), sign(xsk, xm)) = xm++ , checkpcs(xc, pk(xsk), ypk, zpk, pcs(sign(xsk, xc), ypk, zpk)) = true()++ , convertpcs(zsk, pcs(sign(xsk, xc), ypk, pk(zsk))) = sign(xsk, xc)+ /*+ The above two equations are inspired by the following design decisions.+ We model a private signature of a contract 'xc' that is+ - meant for 'y' identified by his public key 'ypk'+ - privately signed by 'x' using his private key 'xsk'+ - to be converted by the trusted party 'z' identified by its public key+ 'zpk'+ using the term 'pcs(sign(xsk, xc), ypk, zpk)'.++ This term chan be checked against 'xc', 'pk(xsk)', ypk, and zpk using+ the 'checkpcs' algorithm.++ It can be converted to a standard signature using the 'convertpcs'+ algorithm provided one has access to the private key of the trusted+ party.++ Note that we embedd the proper standard signature immediately into the+ 'pcs' term, as the resulting equational theory is not subterm-convergent+ otherwise.+ */++// Setting up the trusted third party, i.e., choose its signing key+rule Setup_TTP:+ [ Fr(seed) ] --> [ !TTP(seed), Out(pk(seed)) ]++// Our goal is to check that the TTP cannot be abused to provide the adversary+// with both a certificate that the contract was resolved and a certificate+// that the contract was aborted.++// The TTP answering an 'abort' request.+rule Abort1:+ let msg = <ct, pk1, pk2, pcsig1>+ abortSig = sign(skT, pcsig1)+ in+ [ !TTP(skT)+ , In(<'abort', msg>)+ ]+ --[ // The TTP answers at most once per contract.+ Answered(ct)+ // Check signatures. This is essential. Try uncommenting it and check+ // the resulting attacks.+ , Eq(checkpcs(ct, pk1, pk2, pk(skT), pcsig1), true)+ // Log this action for referencing it in properties+ , Abort1(ct)+ ]->+ [ Out(abortSig) ]++// We refrain from modelling the repeated answering with the same answer.+// It would be easy to model, but does obviously not strengthen the adversary.++// The TTP answering a resolve request by party 2.+rule Resolve2:+ let msg = <ct, pk1, pk2, pcsig1, sig2>+ sig1 = convertpcs(skT, pcsig1)+ resolveSig = sign(skT, <sig1, sig2>)+ in+ [ !TTP(skT)+ , In(<'resolve2', msg>)+ ]+ --[ // The TTP answers at most once per contract.+ Answered(ct)+ // Check signatures+ , Eq(check_getmsg(pk2, sig2), ct)+ , Eq(checkpcs(ct, pk1, pk2, pk(skT), pcsig1), true)+ // Log this action for referencing it in properties+ , Resolve2(ct)+ ]->+ [ Out(resolveSig) ]++// The TTP answering a resolve request by party 1.+rule Resolve1:+ let msg = <ct, pk1, pk2, sig1, pcsig2>+ sig2 = convertpcs(skT, pcsig2)+ resolveSig = sign(skT, <sig1, sig2>)+ in+ [ !TTP(skT)+ , In(<'resolve1', msg>)+ ]+ --[ // The TTP answers at most once per contract.+ Answered(ct)+ // Check signatures+ , Eq(check_getmsg(pk1, sig1), ct)+ , Eq(checkpcs(ct, pk2, pk1, pk(skT), pcsig2), true)+ // Log this action for referencing it in properties+ , Resolve1(ct)+ ]->+ [ Out(resolveSig) ]+++// Witnessing aborted contracts.+rule Witness_Aborted:+ let abortC = sign(skT, pcs(sign(sk1, ct), pk(ysk), pk(skT)))+ in+ [ In(abortC), !TTP(skT) ] --[ AbortCert(ct) ]-> []++// Witnessing resolved contracts.+rule Witness_Resolved:+ let resolveC = sign(skT, <sign(sk1, ct), sign(sk2, ct)>)+ in+ [ In(resolveC), !TTP(skT) ] --[ ResolveCert(ct) ]-> []+++// Axiom: the TTP does not answer any request twice+axiom Answered_unique:+ "All x #i #j. Answered(x) @ i & Answered(x) @ j ==> #i = #j"++// Axiom: the TTP stops if an equality check fails+axiom Eq_checks_succeed: "All x y #i. Eq(x,y) @ i ==> x = y"+++/*+Our desired goal: there is not contract where the adversary can present+both an abort-certificate and a resolve-certificate. This is what is+verified in [1]. It is almost trivial, as it only relies on the uniqueness+check and properly checking the signatures.++TODO: Investigate more interesting properties. Especially properties from the+perspective of the local agents.+*/+lemma aborted_and_resolved_exclusive:+ "not (Ex ct #i #j. AbortCert(ct) @ i & ResolveCert(ct) @ j)"++// Sanity checks: The terms reductions behave as expected.+lemma aborted_contract_reachable:+ exists-trace+ " (Ex ct #i. AbortCert(ct) @ i )+ // Ensure that this is possible with at most one Abort step.+ & (All ct1 ct2 #i1 #i2 .+ Abort1(ct1) @ i1 & Abort1(ct2) @ i2 ==> #i1 = #i2)+ & (All ct #i. Resolve1(ct) @ i ==> F)+ & (All ct #i. Resolve2(ct) @ i ==> F)+ "++lemma resolved1_contract_reachable:+ exists-trace+ " (Ex ct #i. ResolveCert(ct) @ i)+ // Ensure that this is possible with at most one Resolve1 step.+ & (All ct #i. Abort1(ct) @ i ==> F)+ & (All ct1 ct2 #i1 #i2 .+ Resolve1(ct1) @ i1 & Resolve1(ct2) @ i2 ==> #i1 = #i2)+ & (All ct #i. Resolve2(ct) @ i ==> F)+ "++lemma resolved2_contract_reachable:+ exists-trace+ "(Ex ct #i. ResolveCert(ct) @ i)+ // Ensure that this is possible with at most one Resolve1 step.+ & (All ct #i. Abort1(ct) @ i ==> F)+ & (All ct #i. Resolve1(ct) @ i ==> F)+ & (All ct1 ct2 #i1 #i2 .+ Resolve2(ct1) @ i1 & Resolve2(ct2) @ i2 ==> #i1 = #i2)+ "+++/*+Original code from [1]. There is a strange discrepance between the description+of the protocol in [1, Figure 5] and the implementation here. The Abort1+process does not check for a private contract signature, but for a standard+signature. However, the query listed on [1, page 12] considers a TTP-signed+pcs as the abort-certificate.+*/++/*++free c.+free init.+free ok.+free abort.+free resolve1.+free resolve2.+free aborted.+free resolved.+free wtn_contract.+free skA.+free skB.++fun pair/2.+fun pk/1.+fun sign/2.+fun pcs/4.++reduc projl(pair(xl, xr)) = xl.+reduc projr(pair(xl, xr)) = xr.++reduc check_getmsg(pk(xsk), sign(xsk, xm)) = xm.++reduc checkpcs(xc, pk(xsk), ypk, zpk, pcs(xsk, ypk, zpk, xc)) = ok.+reduc convertpcs(zsk, pcs(xsk, ypk, pk(zsk), xc)) = sign(xsk, xc).++let T =+ new skT; (out(c, pk(skT)) | ! C)++let C =+ new s; new ct;+ [s -> pair(init, init)] |+ out(c, ct); in(c, xpk1); in(c, xpk2);+ ( ! Abort1 | ! Resolve2 | ! Resolve1 )++let Abort1 =+ lock;+ in(c, x);+ let xcmd = projl(x) in+ if xcmd = abort then+ let y = projr(x) in+ let yl = projl(y) in+ let ycontract = projl(yl) in+ let yparties = projr(yl) in+ if yparties = pair(xpk1, xpk2) then+ if ycontract = ct then+ let ysig = projr(y) in+ let ym = check_getmsg(xpk1, ysig) in+ if ym = yl then+ read s as ys;+ let ystatus = projl(ys) in+ (if ystatus = aborted then+ let ysigs = projr(ys) in+ out(c, ysigs); unlock) |+ (if ystatus = init then+ s := pair(aborted, sign(skT, y));+ out(c, sign(skT, y)); unlock)++let Resolve2 =+ lock;+ in(c, x);+ let xcmd = projl(x) in+ if xcmd = resolve2 then+ let y = projr(x) in+ let ypcs1 = projl(y) in+ let ysig2 = projr(y) in+ let ycontract = check_getmsg(xpk2, ysig2) in+ if ycontract = ct then+ let ycheck = checkpcs(ct, xpk1, xpk2, pk(skT), ypcs1) in+ if ycheck = ok then+ read s as ys;+ let ystatus = projl(ys) in+ (if ystatus = resolved2 then+ let ysigs = projr(ys) in+ out(c, ysigs); unlock) |+ (if ystatus = init then+ let ysig1 = convertpcs(skT, ypcs1) in+ s := pair(resolved2, sign(skT, pair(ysig1, ysig2)));+ out(c, sign(skT, pair(ysig1, ysig2))); unlock)++let Resolve1 =+ lock;+ in(c, x);+ let xcmd = projl(x) in+ if xcmd = resolve1 then+ let y = projr(x) in+ let ysig1 = projl(y) in+ let ypcs2 = projr(y) in+ let ycontract = check_getmsg(xpk1, ysig1) in+ if ycontract = ct then+ let ycheck = checkpcs(ct, xpk2, xpk1, pk(skT), ypcs2) in+ if ycheck = ok then+ read s as ys;+ let ystatus = projl(ys) in+ (if ystatus = resolved1 then+ let ysigs = projr(ys) in+ out(c, ysigs); unlock) |+ (if ystatus = init then+ let ysig2 = convertpcs(skT, ypcs2) in+ s := pair(resolved1, sign(skT, pair(ysig1, ysig2)));+ out(c, sign(skT, pair(ysig1,ysig2))); unlock)++*/++end
@@ -0,0 +1,149 @@+theory StatVerif_Security_Device begin++/*+ Protocol: Simple security device (Example 1 from [1])+ Modeler: Simon Meier+ Date: May 2012++ Status: working++ This is the simple security device example presented in Section V.A of the+ following paper.++ [1] M. Arapinis, E. Ritter and M. Ryan. StatVerif: Verification of Stateful+ Processes. In CSF'11. IEEE Computer Society Press, pages 33-47 , 2011.++ It models a hardware device that stores both a private key and a+ configuration register that can be set to 'left' for decrypting the first+ component of tuples encrypted using the device's public key and 'right' for+ decrypting the second component of tuples encrypted using the device's+ public key. Alice uses such a device to encrypt tuples such that Bob+ can access either all their first components or all their second+ components, but never both.++ Note that in contrast to [1], we allow the creation of an unbounded number+ of devices. We also verify both the accessibility of left and right+ components, as well as their exclusivity. The source code of the model+ from [1] is attached at the end of this file.++*/++builtins: asymmetric-encryption+++// Create a new device. It stores the private key and publishes the+// corresponding public key.+rule NewDevice:+ [ Fr(~sk) // We let the key identify the device.+ ]+ -->+ [ UnconfiguredDevice(~sk)+ , !DevicePublicKey(pk(~sk))+ , Out(pk(~sk))+ ]++// Alice can use any public key associated to such a hardware security device+// for publishing messages with exclusive access.+rule Alice:+ [ Fr(~x)+ , Fr(~y)+ , !DevicePublicKey(key)+ ]+ --[ Exclusive(~x,~y) ]->+ [ Out( aenc{~x,~y}key )+ ]++// Unconfigured devices can be configured exactly once.+rule ConfigureDevice:+ [ UnconfiguredDevice(sk), In(config) ]+ -->+ [ !ConfiguredDevice(sk, config) ]++// Devices configured to 'left' can be used to decrypt the first component of+// messages encrypted using the device's public key.+rule UseLeftDevice:+ [ !ConfiguredDevice(sk, 'left'), In(aenc{x,y}pk(sk)) ]+ --[ Access(x) ]->+ [ Out(x) ]++// Devices configured to 'right' can be used to decrypt the second component of+// messages encrypted using the device's public key.+rule UseRightDevice:+ [ !ConfiguredDevice(sk, 'right'), In(aenc{x,y}pk(sk)) ]+ --[ Access(y) ]->+ [ Out(y) ]+++// As we use a backwards search, we must specify the possible structure of+// messages sent in 'UseLeftDevice' and 'UseRightDevice' precise enough such+// that we can solve all chain constraints starting from the sent message. We+// therefore log the message being accessed and relate it to its possible+// origins: known to the intruder in an earlier step or part of an exclusive+// message generated by 'Alice'. Typing lemmas are proven by induction and+// incorporated in the case distinction precomputation.+lemma types [typing]:+ "All m #i. Access(m) @ i ==>+ (Ex #j. KU(m) @ j & j < i) // Make use of the KU-facts logged+ // by the construction rules.+ | (Ex x #j. Exclusive(x,m) @ j)+ | (Ex y #j. Exclusive(m,y) @ j)+ "++// Check that there is some trace where the intruder knows the left message of+// an exclusive message-tuple. In contrast to the typing lemma, we use the+// standard 'K'-fact, which is logged by the built-in 'ISend' rule.+lemma reachability_left:+ exists-trace+ "Ex x y #i #j. Exclusive(x,y) @i & K(x) @ j"++lemma reachability_right:+ exists-trace+ "Ex x y #i #k. Exclusive(x,y) @i & K(y) @ k"++// Check that exclusivity is maintained+lemma secrecy:+ "not(Ex x y #i #k1 #k2.+ Exclusive(x,y) @i & K(x) @ k1 & K(y) @ k2+ )+ "++end++/* StatVerif source code of the original model from [1].++fun pair/2.+fun aenc/3.+fun pk/1.+free left.+free right.+free init.+free c.++reduc+ projl(pair(xleft, xright)) = xleft;+ projr(pair(xleft, xright)) = xright;+ adec(u, aenc(pk(u), v, w)) = w.++query+ att:vs,pair(sl,sr).++let device =+ out(c, pk(k)) |+ ( ! lock(s); in(c, x); read s as y;+ if y = init then+ (if x = left then s := x; unlock(s)+ else if x = right then s := x; unlock(s)) ) |+ ( ! lock(s); in(c, x); read s as y; let z = adec(k, x) in+ let zl = projl(z) in+ let zr = projr(z) in+ ((if y = left then out(c, zl); unlock(s)) |+ (if y = right then out(c, zr); unlock(s)))).++let user =+ new sl; new sr; new r;+ out(c, aenc(pk(k), r, pair(sl,sr))).++process+ new k; new s; [s |-> init] | device | ! user++*/
@@ -1,21 +1,32 @@ theory TPM_Envelope begin -text{* Envelope protocol example from:+/*+ Protocol: The Envelope protocol modeled according to [1]+ Modeler: Simon Meier+ Date: September 2012+ Status: Working -[1] Stephanie Delaune, Steve Kremer, Mark D. Ryan, Graham Steel, "Formal-Analysis of Protocols Based on TPM State Registers," csf, pp.66-80, 2011 IEEE-24th Computer Security Foundations Symposium, 2011.+ [1] Stephanie Delaune, Steve Kremer, Mark D. Ryan, Graham Steel, "Formal+ Analysis of Protocols Based on TPM State Registers," csf, pp.66-80, 2011+ IEEE 24th Computer Security Foundations Symposium, 2011. -Modeler: Simon Meier-Date: June 2012-Status: No automatic proof (interactive proof possible)+ Note that this model can also be verified for an arbitrary number of+ reboots. This is an open problem in [1]. The verification relies on the+ construction that we track all writes to the PCR-fact using the additional+ PCR_Write-fact. This allows us then to descend in the hash chain by solving+ PCR_Write-premises. -Note that this model incorporates an arbitrary number of reboots, which is an-open problem in [1]. The verification relies on the construction that we track-all writes to the PCR-fact using the additional PCR_Write-fact. This allows us-then to descend in the hash chain by solving PCR_Write-premises.+ Note also that verification without a reboot takes 19 seconds on an Intel i7+ Quad Core laptop with 4GB RAM. This is two orders of magnitude faster than+ the time reported for [1]; 35min according to+ http://www.lsv.ens-cachan.fr/~delaune/TPM-PCR/. -*}+ The verification with reboot takes 75 seconds on this Intel i7 laptop. The+ key reason why both of these times are so high is that the heuristic has+ trouble discerning between the useful looping goals and the useless ones. A+ manual proof can be much shorter than the one automatically produced.+ Investigating a better heuristic is future work.+*/ builtins: signing, asymmetric-encryption, hashing @@ -35,16 +46,21 @@ , Out(pk(~aik)) // publish the public key of the auth. id. key ] -// reset the PCR to 'pcr0'+// reset the PCR to 'pcr0'. The proof goes also through with reboot, but takes+// considerably longer: about 1m 20sec on my i7 laptop. However, this is only+// a problem with the heuristic. The interactively constructed proof given+// below requires only 28 steps and 0.5 seconds to check.+/* rule PCR_Reboot:- [ PCR(x)- , PCR_Write(x)- ]- --[ PCR_Write('pcr0')- ]->- [ PCR_Write('pcr0')- , PCR('pcr0')- ]+ [ PCR(x)+ , PCR_Write(x)+ ]+ --[ PCR_Write('pcr0')+ ]->+ [ PCR_Write('pcr0')+ , PCR('pcr0')+ ]+*/ // Extend the hash-chain in the PCR rule PCR_Extend:@@ -146,7 +162,16 @@ , Out(pk(~sk)) ] -// Automatically proven+// Axioms; i.e., restrictions on the traces of interest+///////////////////////////////////////////////////////++axiom PCR_Init_unique:+ " All #i #j. PCR_Init() @ i & PCR_Init() @ j ==> #i = #j "++// Security Properties+//////////////////////++// Characterizing the values extractible via unbinding. lemma types [typing]: // Values created by the PCR_Unbind rule " (All m d1 d2 #i. PCR_Unbind(d1, d2, m) @ i ==>@@ -155,7 +180,8 @@ ) " -// Automatically proven+// Every read value was written once. This allows us to reason backwards and+// ensure that the PCR value becomes smaller. lemma PCR_Write_charn [reuse, use_induction]: // Values read from the PCR have been written to it beforehand. " (All x #i. PCR_Read(x) @ i ==>@@ -163,17 +189,107 @@ ) " -// Assuming that there is at most one instance of the PCR,-// the adversary (playing Bob) must not know a secret that Alice created and-// thinks that access to it was denied.-//-// Currently, we have to construct its proof manually. The key argument relies-// on following the PCR_Write-premises once their presence has been-// established via the PCR_Write_charn lemma.-lemma reachable_Denied:- "(All #i #j. PCR_Init() @ i & PCR_Init() @ j ==> #i = #j)- ==>- not(Ex s #i #j #k. Secret(s) @ i & Denied(s) @ j & K(s) @ k)"+// The desired security property: the adversary (Bob) cannot know a secret to+// which he officially denied having access.+lemma Secret_and_Denied_exclusive:+ " not(Ex s #i #j #k. Secret(s) @ i & Denied(s) @ j & K(s) @ k)"+/* Note that the 28 steps of the proof below suffices to justify this lemma+ even with the reboot rule enabled. The heuristic is stymied by the looping+ PCR facts and acts too conservatively, thereby using significantly more+ proof steps (7136) and time (74 seconds).+*/+/*+simplify+solve( Alice1( n ) ▶₀ #i )+ case Alice1+ solve( !AIK( aik ) ▶₂ #i )+ case PCR_Init+ solve( Alice2( n.1, ~s ) ▶₀ #j )+ case Alice2+ solve( !AIK( aik.1 ) ▶₁ #j )+ case PCR_Init+ solve( !KU( sign(<'certkey',+ h(<h(<'pcr0', ~n>), 'obtain'>), pk>,+ ~aik)+ ) @ #vk )+ case PCR_CertKey+ solve( !KU( sign(<'certpcr',+ h(<h(<'pcr0', ~n>), 'deny'>)>,+ ~aik)+ ) @ #vk.1 )+ case PCR_Quote+ solve( PCR_Write( h(<h(<'pcr0', ~n>),+ 'deny'>)+ ) @ #j.2 )+ case PCR_Extend+ solve( !KU( ~s ) @ #vk.2 )+ case Alice2+ by solve( !KU( ~sk ) @ #vk.5 )+ next+ case PCR_Unbind+ solve( !KU( aenc(~s, pk(~sk.1)) ) @ #vk.5 )+ case Alice2+ solve( PCR_Write( h(<h(<'pcr0', ~n>),+ 'obtain'>)+ ) @ #j.3 )+ case PCR_Extend+ solve( PCR_Write( h(<'pcr0', ~n>) ) @ #j.4 )+ case Alice1+ solve( PCR_Write( h(<'pcr0', ~n>) ) @ #j.3 )+ case Alice1+ solve( PCR_Write( 'pcr0' ) ▶₂ #vr )+ case PCR_Init+ solve( PCR_Write( h(<'pcr0', ~n>) ) ▶₀ #j.1 )+ case Alice1+ solve( PCR_Write( h(<'pcr0', ~n>) ) ▶₀ #j.2 )+ case PCR_Extend+ by solve( !KU( ~n ) @ #vk.6 )+ qed+ next+ case PCR_Extend+ by solve( !KU( ~n ) @ #vk.6 )+ qed+ next+ case PCR_Reboot+ solve( PCR_Write( h(<'pcr0', ~n>) ) ▶₀ #j.1 )+ case Alice1+ solve( PCR_Write( h(<'pcr0', ~n>) ) ▶₀ #j.2 )+ case PCR_Extend+ by solve( !KU( ~n ) @ #vk.6 )+ qed+ next+ case PCR_Extend+ by solve( !KU( ~n ) @ #vk.6 )+ qed+ qed+ next+ case PCR_Extend+ by solve( !KU( ~n ) @ #vk.6 )+ qed+ next+ case PCR_Extend+ by solve( !KU( ~n ) @ #vk.6 )+ qed+ qed+ next+ case caenc+ by contradiction+ qed+ qed+ qed+ next+ case csign+ by solve( !KU( ~aik ) @ #vk.5 )+ qed+ next+ case csign+ by solve( !KU( ~aik ) @ #vk.4 )+ qed+ qed+ qed+ qed+qed+*/ end
@@ -1,123 +0,0 @@-theory CSF11_RunningExample begin--text{* Running example from:--Stephanie Delaune, Steve Kremer, Mark D. Ryan, Graham Steel, "Formal Analysis-of Protocols Based on TPM State Registers," csf, pp.66-80, 2011 IEEE 24th-Computer Security Foundations Symposium, 2011.--Modeler: Simon Meier-Date: June 2012-Status: Working--*}--builtins: hashing, asymmetric-encryption, signing--// TPM PCR model-rule PCR_Init:- [ Fr(~aik) // Authentication identity key- ]- --[ PCR_Init('pcr0',~aik)- , UniqueInit() // For removing traces that have multiple initializations- ]->- [ PCR('pcr0') // the initial PCR value is 'pcr0'- , !AIK(~aik) // the auth. id. key is persistent- , Out(pk(~aik)) // publish the public key of the auth. id. key- ]---// Disabled, as the protocol is not secure under reboots.-// TODO: Check that we can find the attack.-//-// Note that we miss the attack, as we do not consider collapsing different-// PCR_Unbind nodes. In order to find this attack, we would require to-// introduce strongly different node variables.-//-// rule PCR_Reboot:-// [ PCR(x) ] --> [ PCR('pcr0') ] // reset the PCR to 'pcr0'--rule PCR_Extend:- [ PCR(x)- , In(y)- ]- --[ PCR_Extend(x,y,h(x,y))- ]->- [ PCR(h(x,y))- ]--rule PCR_CertKey:- [ !AIK(aik)- , !KeyTable(x, sk)- ]- --[ PCR_CertKey_Inst(x)- ]->- [ Out(sign{'certkey', x, pk(sk)}aik) ]--rule PCR_Unbind:- [ PCR(x)- , !KeyTable(x, sk)- , In( aenc{m}pk(sk) )- ]- --[ PCR_Unbind(x,sk,m)- ]->- [ PCR(x)- , Out(m) ]--// Alice-rule Alice_Init:- [ Fr(~s0)- , Fr(~s1)- , !AIK(aik)- , In(sign{'certkey', x0, pk0}aik)- , In(sign{'certkey', x1, pk1}aik)- ]- --[ Ineq(x0, x1)- , Secrets(~s0,~s1)- ]->- [ Out(aenc{~s0}pk0)- , Out(aenc{~s1}pk1)- ]--// Keytable-rule MkKey:- // Fr(<'MkKey',$a>) // register at most one key per public constant- [ Fr(~ska) ]- -->- [ !KeyTable(h('pcr0',$a), ~ska) ]--lemma types [typing]:- " (All m d1 d2 #i. PCR_Unbind(d1, d2, m) @ i ==>- (Ex #j. KU(m) @ j & j < i)- | (Ex s #j. Secrets(m, s) @ j)- | (Ex s #j. Secrets(s, m) @ j)- )- "--lemma Unbind_PCR_Value [reuse, use_induction]:- "All x sk m #i.- PCR_Unbind(x, sk, m) @ i- ==>- ( (Ex aik #j. PCR_Init(x, aik) @ j )- | (Ex y xPrev #j. PCR_Extend(xPrev,y,x) @ j)- | (Ex #i #j. UniqueInit() @ j & UniqueInit() @ i & not (#i = #j))- )- "--lemma secrecy:- " ( (All #i #j. UniqueInit() @ j & UniqueInit() @ i ==> #i = #j)- & (All t #e. Ineq(t,t) @ e ==> F)- ) ==>- not( (Ex s0 s1 #i #d0 #d1.- Secrets(s0, s1) @ i- & K(s0) @ d0- & K(s1) @ d1- ) )"--------end
@@ -0,0 +1,167 @@+theory TPM_Exclusive_Secrets begin++/*+ Protocol: Running example from [1]+ Modeler: Simon Meier+ Date: September 2012+ Status: Working++ [1] Stephanie Delaune, Steve Kremer, Mark D. Ryan, Graham Steel, "Formal+ Analysis of Protocols Based on TPM State Registers," csf, pp.66-80, 2011+ IEEE 24th Computer Security Foundations Symposium, 2011.++ The goal of this example is to verify that the adversary cannot exploit+ his TPM to simultainously access the two secrets that were encryped+ exclusively by Alice.++ Note that we could easily model multiple PCR's, if required.++*/++builtins: hashing, asymmetric-encryption, signing++// TPM Model with support for a single PCRs+///////////////////////////////////////////++rule PCR_Init:+ [ Fr(~aik) // Authentication identity key+ ]+ --[ PCR_Init('pcr0',~aik)+ , UniqueInit() // For removing traces that have multiple initializations+ ]->+ [ PCR('pcr0') // the initial PCR value is 'pcr0'+ , !AIK(~aik) // the auth. id. key is persistent+ , Out(pk(~aik)) // publish the public key of the attest. ident. key+ ]+++// Disabled, as the protocol is not secure under reboots.+//+// Note that we miss the attack, as we do not consider collapsing different+// PCR_Unbind nodes by default. The general construction would require+// distinctness constraints on temporal variables. We can however simulate it+// by proving a simple case distinction lemma with a 'reuse' attribute, as+// demonstrated below.+//+// rule PCR_Reboot:+// [ PCR(x) ] --> [ PCR('pcr0') ] // reset the PCR to 'pcr0'++// Extending the PCR register with the value 'y'+rule PCR_Extend:+ [ PCR(x) , In(y) ] --[ PCR_Extend(x,y,h(x,y)) ]-> [ PCR(h(x,y)) ]++// Create a fresh key that is bound to 'pcr0' extended with a public+// constant.+rule PCR_CreateKey:+ [ Fr(~ska) ] --> [ !KeyTable(h('pcr0',$a), ~ska) ]++// Certifying a key using the TPM's Attestation Identity Key (AIK)+rule PCR_CertKey:+ [ !AIK(aik)+ , !KeyTable(x, sk) // Any key in the keytable can be certified.+ ]+ --[ PCR_CertKey_Inst(x)+ ]->+ [ Out(sign{'certkey', x, pk(sk)}aik) ]++// Keys in the keytable are bound to a fixed PCR value. If this value, agrees+// with the actual PCR value, then the TPM can be used to decrypt messages+// encrypted with these keys.+rule PCR_Unbind:+ [ PCR(x)+ , !KeyTable(x, sk)+ , In( aenc{m}pk(sk) )+ ]+ --[ PCR_Unbind(x,sk,m)+ ]->+ [ PCR(x) , Out(m) ]++// Alice generates two secrets and accepts two *different* keys signed by the+// TPM to provide exlusive access to them. We are a bit lazy here and use+// pattern matching for the signature verification.+rule Alice_Init:+ [ Fr(~s0)+ , Fr(~s1)+ , !AIK(aik)+ , In(sign{'certkey', x0, pk0}aik)+ , In(sign{'certkey', x1, pk1}aik)+ ]+ --[ InEq(x0, x1)+ , Secrets(~s0,~s1)+ ]->+ [ Out(aenc{~s0}pk0)+ , Out(aenc{~s1}pk1)+ ]++++// Axioms; i.e., restrictions on the set of traces of interest+//////////////////////////////////////////////////////////////++axiom UniqueInit_unique:+ " All #i #j. UniqueInit() @ j & UniqueInit() @ i ==> #i = #j "++axiom Ineq_checks_succeed:+ " All t #e. InEq(t,t) @ e ==> F "+++// Security Properties+//////////////////////+++// A type invariant characterizing the values that can be learned using the+// TPM to Unbind (i.e., decrypt) messages.+lemma types [typing]:+ " (All m d1 d2 #i. PCR_Unbind(d1, d2, m) @ i ==>+ (Ex #j. KU(m) @ j & j < i)+ | (Ex s #j. Secrets(m, s) @ j)+ | (Ex s #j. Secrets(s, m) @ j)+ )+ "++// Characterizing the unbinding operation. This is the key lemma. It allows us+// to jump backwards to smaller values of the PCR register during reasoning.+lemma Unbind_PCR_charn [reuse, use_induction]:+ "All x sk m #i.+ // If the key 'sk' bound to PCR value 'x' is used to extract the body+ // 'm' of an encryption, then+ PCR_Unbind(x, sk, m) @ i+ ==>+ // 'x' is the initial PCR value+ ( (Ex aik #j. PCR_Init(x, aik) @ j )+ // or it was the result of an extension.+ | (Ex y xPrev #j. PCR_Extend(xPrev,y,x) @ j)+ )+ "++// Uncomment to perform case distinctions on the identity of different+// PCR_Unbind nodes. This is required to find the attack when using reboots.+/*+lemma PCR_Unbind_case_distinctions [reuse]:+ "All d11 d21 m1 #i1 d12 d22 m2 #i2.+ PCR_Unbind(d11, d21, m1) @ i1+ & PCR_Unbind(d12, d22, m2) @ i2+ ==>+ (#i1 = #i2) | not(#i1 = #i2)+ "+*/++// The desired security property+lemma exclusive_secrets:+ " not(Ex s0 s1 #i #d0 #d1.+ Secrets(s0, s1) @ i+ & K(s0) @ d0+ & K(s1) @ d1+ )"++// Sanity check: both secrets can be accessed individually.+lemma left_reachable:+ exists-trace+ " Ex s0 s1 #i #j. Secrets(s0, s1) @ i & K(s0) @ j "++lemma right_reachable:+ exists-trace+ " Ex s0 s1 #i #j. Secrets(s0, s1) @ i & K(s1) @ j "+++end
@@ -0,0 +1,227 @@+theory Yubikey+begin++section{* The Yubikey-Protocol *}++/*+ * Protocol: Yubikey Protocol+ * Modeler: Robert Kunnemann, Graham Steel+ * Date: August 2012+ *+ * Status: working+ */++builtins: symmetric-encryption++functions: S/1,zero/0++/* We to model the Yubikey protocol, described in+* http://www.yubico.com/documentation+* http://www.yubico.com/developers-intro+* This is simplified version, in particular:+* - timestamps are not modelled+* - We do not distinguish the session and token counter. We describe them+* as one single counter, that represents the pair (session counter, token+* counter) with a lexicographical order on the pair. This implies that+* a) pressing the button on the Yubikey increases this counter by 1, and+* b) a plugin of the Yubikey increases it by an arbitrary amount the+* adversary gets to choose (giving him more power).+*/++/* The following rules model two binary relations between integers. !Succ+ * is functional. If !Succ(a,b), then the adversary was able to show that b+ * is the successor of b. Similarly, albeit !Smaller is not functional, if+ * !Smaller(a,b), then the adversary was able to show that a is smaller+ * than b.+ * The Theory() action is used to enforce that this relation (to the extend+ * it is needed in this trace) has to be build up before running the first+ * protocol actions.+*/+rule InitSucc:+ [In(zero),In(S(zero))]+ --[Theory(), IsSucc(zero,S(zero)),IsZero(zero)]->+ [!Succ(zero,S(zero))]++rule StepSucc:+ [In(y),In(S(y)), !Succ(x,y)]+ --[Theory(), IsSucc(y,S(y)) ]->+ [!Succ(y,S(y))]++rule SimpleSmaller:+ [!Succ(x,y)]+ --[Theory(), IsSmaller(x,y)]->+ [!Smaller(x,y)]++rule ZExtendedSmaller:+ [!Smaller(x,y),!Succ(y,z)]+ --[Theory(), IsSmaller(x,z)]->+ [!Smaller(x,z)]++/* A Yubikey is initialised with a zero counter, a key and a public, as well as a+ * secret identifier, ~pid and ~sid. This information is shared with the+ * Authentication Server, so we assume a trusted way of installing a+ * Yubikey+*/+rule BuyANewYubikey:+ [Fr(~k),Fr(~pid),Fr(~sid)] //for fresh k, public and secret id..+ --[Protocol(), Init(~pid,~k),ExtendedInit(~pid,~sid,~k),IsZero(zero)]->+ [!Y(~pid,~sid), Y_counter(~pid,zero),+ //..store public and secret id along with the starting counter+ //(zero) on the Yubikey..+ Server(~pid,~sid,zero),!SharedKey(~pid,~k), //and on the server+ Out(~pid)] //and make the public id public++//On plugin, the session counter is increased and the token counter reset+rule Yubikey_Plugin:+ [Y_counter(pid,sc),!Smaller(sc, Ssc) ] + //The old counter value sc is removed+ --[ Yubi(pid,Ssc) ]-> + [Y_counter(pid, Ssc)]+ //and substituted by a new counter value, larger, Ssc++//If the Button is pressed, the token counter is increased+rule Yubikey_PressButton:+ [!Y(pid,sid), Y_counter(pid,tc),!SharedKey(pid,k),+ !Succ(tc,Stc),Fr(~npr),Fr(~nonce) ]+ //The old countervalue tc is removed+ --[ YubiPress(pid,tc), YubiOTP(pid,senc(<sid,tc,~npr>,k)),+ YubiSid(pid,sid,k) ]->+ [Y_counter(pid, Stc), //and substituted by its successor+ Out(<pid,~nonce,senc(<sid,tc,~npr>,k)>) + //in addition, an encrypted otp is output along with a nonce and+ //the public id of the Yubikey used.+ ]++/* Upon receiving an encrypted OTP, the Server compares the (unencrypted)+ * public id to his data base to identify the key to decrypt the OTP. After+ * making sure that the secret id is correct, the Server verifies that the+ * received counter value is larger than the last one stored. If the Login+ * is successful, i.e., the previous conditions were fulfilled, the counter+ * value on the Server that is associated to the Yubikey is updated.+ */++rule Server_ReceiveOTP_NewSession:+ [Server(pid,sid,otc), In(<pid,nonce,senc(<sid,tc,~pr>,k)>),+ !SharedKey(pid,k), !Smaller(otc,tc) ]+ //if the Server receives an OTP encrypted with k that belongs to+ //the (unencrypted) public id, and the OTP has the right format,+ //contains the correct secret id as well as a counter tc that is+ //larger than the current counter otc, then...+ --[ Login(pid,sid,tc,senc(<sid,tc,~pr>,k)),+ LoginCounter(pid,otc,tc) //..the Login is accepted..+ ]->+ [Server(pid,sid,tc)] //..and the counter value updated.++/* The following three axioms are conditions on the traces that make sure+ * that : */++//a) the !Smaller relation is transitive+axiom transitivity: //axiomatic+ "All #t1 #t2 a b c. IsSmaller(a,b)@t1 & IsSmaller(b,c)@t2+ ==> Ex #t3 . IsSmaller(a,c)@t3 "++//b) !Smaller implies unequality+axiom smaller_implies_unequal: //axiomatic+ "not (Ex a #t . IsSmaller(a,a)@t)"++//c) The protocol runs only after the IsSmaller and IsSuccessor relation is+// build up+axiom theory_before_protocol: + "All #i #j. Theory() @ i & Protocol() @ j ==> i < j"++// For sanity: Ensure that a successful login is reachable.+lemma Login_reachable:+ exists-trace+ "Ex #i pid sid x otp1. Login(pid,sid,x,otp1)@i"++// Each succesful login with counter value x was preceeded by a PressButton+// event with the same counter value+lemma one_count_foreach_login[reuse,use_induction]:+ "All pid sid x otp #t2 . Login(pid,sid,x,otp)@t2 ==>+ ( Ex #t1 . YubiPress(pid,x)@#t1 & #t1<#t2 )"++// If a succesful Login happens before a second sucesfull Login, the+// counter value of the first is smaller than the counter value of the+// second+lemma slightly_weaker_invariant[reuse, use_induction]:+ "(All pid otc1 tc1 otc2 tc2 #t1 #t2 .+ LoginCounter(pid,otc1,tc1)@#t1 & LoginCounter(pid,otc2,tc2)@#t2+ ==> ( #t1<#t2 & ( Ex #t3 . IsSmaller(tc1,tc2)@t3 ))+ | #t2<#t1 | #t1=#t2)+ "+induction+ case empty_trace+ by contradiction // from formulas+next+ case non_empty_trace+ simplify+ solve( (∀ pid otc1 tc1 otc2 tc2 #t1 #t2.+ (LoginCounter( pid, otc1, tc1 ) @ #t1) ∧+ (LoginCounter( pid, otc2, tc2 ) @ #t2)+ ⇒+ (last(#t2)) ∨+ (last(#t1)) ∨+ ((#t1 < #t2) ∧+ (∃ #t3. (IsSmaller( tc1, tc2 ) @ #t3) ∧ ¬(last(#t3)))) ∨+ (#t2 < #t1) ∨+ (#t1 = #t2)) ∥+ (∃ #t1 #t2 a b c.+ (IsSmaller( a, b ) @ #t1) ∧ (IsSmaller( b, c ) @ #t2)+ ∧+ (¬(last(#t2))) ∧+ (¬(last(#t1))) ∧+ (∀ #t3. (IsSmaller( a, c ) @ #t3) ⇒ last(#t3))) )+ case case_1+ solve( (last(#t2)) ∥ (last(#t1)) ∥+ ((#t1 < #t2) ∧+ (∃ #t3. (IsSmaller( tc1, tc2 ) @ #t3) ∧ ¬(last(#t3)))) ∥+ (#t2 < #t1) ∥ (#t1 = #t2) )+ case case_1+ solve( Server( pid, sid, otc1 ) ▶₀ #t1 )+ case BuyANewYubikey+ solve( Server( ~pid, sid.1, otc2 ) ▶₀ #t2 )+ by sorry+ next+ case Server_ReceiveOTP_NewSession_case_1+ solve( Server( ~pid, sid.1, otc2 ) ▶₀ #t2 )+ by sorry+ next+ case Server_ReceiveOTP_NewSession_case_2+ solve( Server( ~pid, sid.1, otc2 ) ▶₀ #t2 )+ by sorry+ next+ case Server_ReceiveOTP_NewSession_case_3+ solve( Server( ~pid, sid.1, otc2 ) ▶₀ #t2 )+ by sorry+ next+ case Server_ReceiveOTP_NewSession_case_4+ solve( Server( ~pid, sid.1, otc2 ) ▶₀ #t2 )+ by sorry+ qed+ next+ case case_2+ by contradiction // cyclic+ next+ case case_3+ by contradiction // from formulas+ next+ case case_4+ by contradiction // from formulas+ next+ case case_5+ by contradiction // from formulas+ qed+ next+ case case_2+ by sorry+ qed+qed++// It is not possible to have to distinct logins with the same counter+// value+lemma no_replay:+ "not (Ex #i #j pid sid x otp1 otp2 .+ Login(pid,sid,x,otp1)@i & Login(pid,sid,x,otp2)@j + & not(#i=#j))"+end+
@@ -0,0 +1,277 @@+theory YubikeyHSM+begin++section{* The Yubikey-Protocol with a YubiHSM *}++/*+ * Protocol: Yubikey Protocol with a YubiHSM+ * Modeler: Robert Kunnemann, Graham Steel+ * Date: August 2012+ *+ * Status: working+ */++builtins: symmetric-encryption++functions: S/1,zero/0++/* We to model the Yubikey protocol, described in+* http://www.yubico.com/documentation+* http://www.yubico.com/developers-intro+* In this version, we assume the Authentication Server to be under the+* control of the attacker. We investigate the secrecy of keys in case the+* Authentication Server can protect the keys by encrypting them using a+* Hardware Token called YubiHSM, see:+* TODO URL will follow+* This is simplified version, in particular:+* - timestamps are not modelled+* - we do not distinguish the session and token counter. We described them+* as one single counter, that represents the pair (session counter, token+* counter) with a lexicographical oder on the pair.+* - we model encryption in more detail than the Theory Yubikey. However,+* we use a very much simplified model of XOR+* - we assume the YubiHSM to be in a configuration where only the flags+* YSM_AEAD_RANDOM_GENERATE and+* YSM_AEAD_YUBIKEY_OTP_DECODE+* are activated.+*/++++/* keystream models the way the keystream used for encryption is computed.+ * Mac describes the MAC used inside the AEADs, which are computed using+ * CBC mode, described in RFC 3610.+ * keystream_kh and keyhandle_n model the adversaries capacity to extract the+ * used keyhandle and nonce that determined the keystream. (Similar for+ * demac.)+*/+functions: keystream/2, keystream_kh/1, keystream_n/1,+ xorc/2, dexor1/2, dexor2/2,+ mac/2, demac/2+equations: keystream_kh(keystream(kh,n))=kh,+ keystream_n(keystream(n,n))=n,+/* an incomplete way of modelling the algebraic properties of the XOR+ * operator */+ dexor1(xorc(a,b),a)=b,+ dexor2(xorc(a,b),b)=a,+/* using mac, adv might find out *something* about the message, we+ * overapproximate */+ demac(mac(m,k),k)=m++/* The following rules model two binary relations between integers. !Succ+ * is functional. If !Succ(a,b), then the adversary was able to show that b+ * is the successor of b. Similarly, albeit !Smaller is not functional, if+ * !Smaller(a,b), then the adversary was able to show that a is smaller+ * than b.+ * The Theory() action is used to enforce that this relation (to the extend+ * it is needed in this trace) has to be build up before running the first+ * protocol actions.+*/+rule InitSucc:+ [In(zero),In(S(zero))]+ --[Theory(), IsSucc(zero,S(zero)),IsZero(zero)]->+ [!Succ(zero,S(zero))]++rule StepSucc:+ [In(y),In(S(y)), !Succ(x,y)]+ --[Theory(), IsSucc(y,S(y)) ]->+ [!Succ(y,S(y))]++rule SimpleSmaller:+ [!Succ(x,y)]+ --[Theory(), IsSmaller(x,y)]->+ [!Smaller(x,y)]++rule ZExtendedSmaller:+ [!Smaller(x,y),!Succ(y,z)]+ --[Theory(), IsSmaller(x,z)]->+ [!Smaller(x,z)]++// Rules for intruder's control over Server++/* The attacker can send messages to the HSM, i.e., on behalf of the+ * authentication server. Likewise, he can receive messages.+ */++rule isendHSM:+ [ In( x ) ] --[ HSMWrite(x) ]-> [ InHSM( x ) ]+rule irecvHSM:+ [ OutHSM( x ) ] --[ HSMRead(x) ]-> [Out(x)]++/* The attacker can write and read the Authentication Server's database.+ * This database contains a list of public ideas and corresponding AEADs+ */+rule read_AEAD:+ [ !S_AEAD(pid,aead) ] --[ AEADRead(aead),HSMRead(aead) ]-> [Out(aead)]+rule write_AEAD:+ [ In(aead), In(pid) ] --[ AEADWrite(aead),HSMWrite(aead) ]-> [!S_AEAD(pid,aead) ]+++/* Initialisation of HSM and Authentication Server. OneTime() Assures that+ * this can only happen a single time in a trace */+rule HSMInit:+ [Fr(~k), Fr(~kh)] --[Protocol(), GenerateRole1(~k),MasterKey(~k), OneTime()]->+ [ !HSM(~kh,~k), Out(~kh),+/* If the following line is uncommented, we are able to reproduce the+ * attack described in+ * http://static.yubico.com/var/uploads/pdfs/Security%20Advisory.pdf+ */+//!YSM_AEAD_GENERATE(~kh), //uncomment to produce attacks+!YSM_AEAD_YUBIKEY_OTP_DECODE(~kh)+]++//Some commands on the HSM:+rule YSM_AEAD_RANDOM_GENERATE:+ let ks=keystream(kh,N)+ aead=<xorc(senc(ks,k),~data),mac(~data,k)>+ in+ [Fr(~data), InHSM(<N,kh>),!HSM(kh,k),!YSM_AEAD_RANDOM_GENERATE(kh)]+ --[GenerateRandomAEAD(~data)]->+ [OutHSM( aead)+ ]++rule YSM_AEAD_GENERATE:+ let ks=keystream(kh,N)+ aead=<xorc(senc(ks,k),data),mac(data,k)>+ in+ [InHSM(<N,kh,data>),!HSM(kh,k),!YSM_AEAD_GENERATE(kh)]+ --[GenerateAEAD(data,aead )]->+ [OutHSM( aead) ]++rule YSM_AES_ESC_BLOCK_ENCRYPT:+ [InHSM(<kh,data>), !HSM(kh,k), !YSM_AES_ESC_BLOCK_ENCRYPT(kh)]+ --[]->+ [OutHSM(senc(data,k))]++rule YSM_AEAD_YUBIKEY_OTP_DECODE:+ let ks=keystream(kh,N)+ aead=<xorc(senc(ks,k),<k2,did>),mac(<k2,did>,k)>+ otp=senc(<did,sc,rand>,k2)+ in+ [InHSM(<did,kh,aead,otp>), !HSM(kh,k), !YSM_AEAD_YUBIKEY_OTP_DECODE(kh)+ ]+ --[+ OtpDecode(k2,k,<did,sc,rand>,sc,xorc(senc(ks,k),<k2,did>),mac(<k2,did>,k)),+ OtpDecodeMaster(k2,k)+ ]->+ [OutHSM(sc)]++//Yubikey operations+//(see Yubikey.spthy for more detailed comments)+rule BuyANewYubikey:+ let ks=keystream(kh,~pid)+ aead=<xorc(senc(ks,~k),<~k2,~sid>),mac(<~k2,~sid>,~k)>+ in+/* This rule implicitly uses YSM_AEAD_GENERATE to produce the AEAD that+ * stores the secret identity and shared key of a Yubikey. By disabling the+ * YSM_AEAD_GENERATE flag but nevertheless permitting this operation, we+ * model a scenario where YSM_AEAD_GENERATE can be safely used to guarantee+ * the operation, but not by the attacker. This corresponds to a scenario+ * where Yubikey set-up takes place on a different server, or where the+ * set-up takes place before the server is plugged into the network.+ * Uncomment the following line to require the HSM to have the+ * YSM_AEAD_GENERATE flag set.+ */+//!YSM_AEAD_GENERATE(kh),+ [ Fr(~k2),Fr(~pid),Fr(~sid),+ !HSM(kh,~k),+ !Succ(zero,one) ]+ --[Init(~pid,~k2)]->+ [Y_counter(~pid,one), !Y_Key(~pid,~k2), !Y_sid(~pid,~sid),+ S_Counter(~pid,zero), !S_AEAD(~pid,aead), !S_sid(~pid,~sid),+ Out(~pid) ]++//On plugin, the session counter is increased and the token counter reset+rule Yubikey_Plugin:+ [Y_counter(pid,sc),!Smaller(sc, Ssc) ]+ //The old counter value sc is removed+ --[ Yubi(pid,Ssc) ]->+ [Y_counter(pid, Ssc)]+ //and substituted by a new counter value, larger, Ssc++rule Yubikey_PressButton:+ [Y_counter(pid,tc),!Y_Key(pid,k2),!Y_sid(pid,sid),+ !Succ(tc,Stc),Fr(~pr),Fr(~nonce) ]+ --[ YubiPress(pid,tc),+ YubiPressOtp(pid,<sid,tc,~pr>,tc,k2) ]->+ [Y_counter(pid,Stc), Out(<pid,~nonce,senc(<sid,tc,~pr>,k2)>)]++rule Server_ReceiveOTP_NewSession:+ let ks=keystream(kh,pid)+ aead=<xorc(senc(ks,k),<k2,sid>),mac(<k2,sid>,k)>+ in+ [In(<pid,nonce,senc(<sid,tc,~pr>,k2)>) ,+ !HSM(kh,k), !S_AEAD(pid,aead), S_Counter(pid,otc),+ !S_sid(pid,sid), !Smaller(otc,tc) ]+ --[ Login(pid,sid,tc,senc(<sid,tc,~pr>,k2)) ]->+ [ S_Counter(pid,tc) ]++/* The following three axioms are conditions on the traces that make sure+ * that : */+//+//a) the !Smaller relation is transitive+axiom transitivity: //axiomatic+ "All #t1 #t2 a b c. IsSmaller(a,b)@t1 & IsSmaller(b,c)@t2+ ==> Ex #t3 . IsSmaller(a,c)@t3 "++//b) !Smaller implies unequality+axiom smaller_implies_unequal: //axiomatic+ "not (Ex a #t . IsSmaller(a,a)@t)"++/*c) The protocol runs only after the IsSmaller and IsSuccessor relation is+ * build up+ */+axiom theory_before_protocol:+ "All #i #j. Theory() @ i & Protocol() @ j ==> i < j"++axiom onetime:+ "All #t3 #t4 . OneTime()@#t3 & OneTime()@t4 ==> #t3=#t4"++//LEMMAS:++// For sanity: Ensure that a successful login is reachable.+//TODO reactivate+//lemma Login_reachable:+// exists-trace+// "Ex #i pid sid x otp1. Login(pid,sid, x, otp1)@i"++/* Every counter produced by a Yubikey could be computed by the adversary+ * anyway. (This saves a lot of steps in the backwards induction of the+ * following lemmas).+*/+lemma adv_can_guess_counter[reuse,use_induction]:+ "All pid sc #t2 . YubiPress(pid,sc)@t2+ ==> (Ex #t1 . K(sc)@#t1 & #t1<#t2)"++/* Everything that can be learned by using OtpDecode is the counter of a+ * Yubikey, which can be computed according to the previous lemma.+*/+lemma otp_decode_does_not_help_adv_use_induction[reuse,use_induction]:+ "All #t3 k2 k m sc enc mac . OtpDecode(k2,k,m,sc,enc,mac)@t3+ ==> Ex #t1 pid . YubiPress(pid,sc)@#t1 & #t1<#t3"++/* All keys shared between the YubiHSM and the Authentication Server are+ * either not known to the adversary, or the adversary learned the key used+ * to encrypt said keys in form of AEADs.+ */+lemma k2_is_secret_use_induction[use_induction,reuse]:+ "All #t1 #t2 pid k2 . Init(pid,k2)@t1 & K(k2)@t2+ ==>+ (Ex #t3 #t4 k . K(k)@t3 & MasterKey(k)@t4 & #t3<#t2)"++/* Neither of those kinds of keys are ever learned by the adversary */+lemma neither_k_nor_k2_are_ever_leaked_inv[use_induction,reuse]:+ "+not( Ex #t1 #t2 k . MasterKey(k)@t1 & K(k)@t2 )+& not (Ex #t5 #t6 k6 pid . Init(pid,k6)@t5 & K(k6)@t6 )+ "++// Each succesful login with counter value x was preceeded by a PressButton+// event with the same counter value+// This lemma cannot be proven at the moment, but it would be a first step+// to reach the no_replay result present in Yubikey.spthy+//lemma one_count_foreach_login[reuse,use_induction]:+// "All pid sid x otp #t2 . Login(pid,sid,x,otp)@t2 ==>+// ( Ex #t1 . YubiPress(pid,x)@#t1 & #t1<#t2 )"++end
interactive-only-src/Paths_tamarin_prover.hs view
@@ -12,7 +12,7 @@ version :: Version-version = Version {versionBranch = [0,8,1,0], versionTags = []}+version = Version {versionBranch = [0,8,2,0], versionTags = []} bindir, libdir, datadir, libexecdir :: FilePath bindir = "./"
src/Main/Mode/Intruder.hs view
@@ -18,11 +18,11 @@ import System.FilePath import Theory-import Theory.Text.Parser (intruderVariantsFile) import Theory.Tools.IntruderRules import Main.Console import Main.Environment+import Main.TheoryLoader (intruderVariantsFile) import Main.Utils
src/Main/Mode/Test.hs view
@@ -23,7 +23,7 @@ import qualified Term.UnitTests as Term (tests) import Theory-import qualified Theory.Text.Parser.UnitTests as Parser+import qualified Test.ParserTests as Parser -- | Self-test mode.
src/Main/TheoryLoader.hs view
@@ -24,27 +24,37 @@ , closeThy + -- ** Message deduction variants+ , intruderVariantsFile+ , addMessageDeductionRuleVariants+ ) where import Prelude hiding (id, (.)) import Data.Char (toLower)+import Data.Label+import Data.List (isPrefixOf) import Data.Monoid import Control.Basics import Control.Category-import Control.DeepSeq (rnf)+import Control.DeepSeq (rnf)+import Extension.Prelude (ifM) import System.Console.CmdArgs.Explicit+import System.Directory (doesFileExist) import Theory import Theory.Text.Parser import Theory.Text.Pretty import Theory.Tools.AbstractInterpretation (EvaluationStyle(..))+import Theory.Tools.IntruderRules (specialIntruderRules, subtermIntruderRules) import Theory.Tools.Wellformedness import Main.Console import Main.Environment+import Paths_tamarin_prover (getDataFileName) ------------------------------------------------------------------------------@@ -55,8 +65,8 @@ -- | Flags for loading a theory. theoryLoadFlags :: [Flag Arguments] theoryLoadFlags =- [ flagNone ["prove"] (addEmptyArg "addProofs")- "Attempt to prove all security properties"+ [ flagOpt "" ["prove"] (updateArg "prove") "LEMMAPREFIX"+ "Attempt to prove a lemma " , flagOpt "dfs" ["stop-on-trace"] (updateArg "stopOnTrace") "DFS|BFS|NONE" "How to search for traces (default DFS)"@@ -142,7 +152,7 @@ -- | Close a theory according to arguments. closeThy :: Arguments -> OpenTheory -> IO ClosedTheory closeThy as =- fmap (proveTheory prover . partialEvaluation)+ fmap (proveTheory lemmaSelector prover . partialEvaluation) . closeTheory (maudePath as) -- FIXME: wf-check is at the wrong position here. Needs to be more -- fine-grained.@@ -163,11 +173,17 @@ noteWellformedness (checkWellformedness thy) thy + lemmaSelector :: Lemma p -> Bool+ lemmaSelector lem =+ any (`isPrefixOf` get lName lem) lemmaNames+ where+ lemmaNames = findArg "prove" as+ -- replace all annotated sorrys with the configured autoprover. prover :: Prover- prover | argExists "addProofs" as =+ prover | argExists "prove" as = replaceSorryProver $ runAutoProver $ constructAutoProver as- | otherwise = mempty+ | otherwise = mempty -- | Construct an 'AutoProver' from the given arguments (--bound, -- --stop-on-trace).@@ -203,3 +219,31 @@ Just "none" -> CutNothing Just "bfs" -> CutBFS Just other -> error $ "unknown stop-on-trace method: " ++ other+++------------------------------------------------------------------------------+-- Message deduction variants cached in files+------------------------------------------------------------------------------++-- | The name of the intruder variants file.+intruderVariantsFile :: FilePath+intruderVariantsFile = "intruder_variants_dh.spthy"++-- | Add the variants of the message deduction rule. Uses the cached version+-- of the @"intruder_variants_dh.spthy"@ file for the variants of the message+-- deduction rules for Diffie-Hellman exponentiation.+addMessageDeductionRuleVariants :: OpenTheory -> IO OpenTheory+addMessageDeductionRuleVariants thy0+ | enableDH msig = do+ variantsFile <- getDataFileName intruderVariantsFile+ ifM (doesFileExist variantsFile)+ (do dhVariants <- parseIntruderRulesDH variantsFile+ return $ addIntrRuleACs dhVariants thy+ )+ (error $ "could not find intruder message deduction theory '"+ ++ variantsFile ++ "'")+ | otherwise = return thy+ where+ msig = get (sigpMaudeSig . thySignature) thy0+ rules = subtermIntruderRules msig ++ specialIntruderRules+ thy = addIntrRuleACs rules thy0
+ src/Test/ParserTests.hs view
@@ -0,0 +1,92 @@+-- |+-- Copyright : (c) 2012 Simon Meier+-- License : GPL v3 (see LICENSE)+--+-- Maintainer : Simon Meier <iridcode@gmail.com>+--+-- Unit tests for checking that all examples parse properly.+module Test.ParserTests (++ testParseFile+ , testParseDirectory+ ) where++import Test.HUnit++import Control.Basics++import System.Directory+import System.FilePath++import Theory+import Theory.Text.Parser+import Theory.Text.Pretty (render)+import Main.TheoryLoader (addMessageDeductionRuleVariants)++-- | Test wether a given file exists, can be parsed, and can still be parsed+-- after being pretty printed.+testParseFile :: Maybe (FilePath, Prover)+ -- ^ Path to maude and prover for testing whether proof parsing+ -- works properly.+ -> FilePath+ -- ^ File on which to test parsing (and proving)+ -> Test+testParseFile optionalProver inpFile = TestLabel inpFile $ TestCase $ do+ thyString <- readFile inpFile+ thy0 <- parse "original file:" thyString+ -- add proofs and pretty print closed theory, if desired+ (thy, thyPretty) <- case optionalProver of+ Nothing ->+ return (thy0, prettyOpenTheory thy0)+ Just (maudePath, prover) -> do+ closedThy <- proveTheory (const True) prover <$> closeTheory maudePath thy0+ return $ ( normalizeTheory $ openTheory closedThy+ , prettyClosedTheory closedThy)+ thy' <- parse "pretty printed theory:" (render thyPretty)+ unless (thy == thy') $ do+ let (diff1, diff2) =+ unzip $ dropWhile (uncurry (==)) $ zip (show thy) (show thy')+ assertFailure $ unlines+ [ "Original theory", "", render (prettyOpenTheory thy), ""+ , "Pretty printed and parsed" , "", render (prettyOpenTheory thy'), ""+ , "Original theory (diff)", "", indent diff1, ""+ , "Pretty printed and parsed (diff)" , "", indent diff2, "", "DIFFER"+ ]+ return ()+ where+ indent = unlines . map (' ' :) . lines++ parse msg str = case parseOpenTheoryString [] str of+ Left err -> do assertFailure $ withLineNumbers $ indent $ show err+ return (error "testParseFile: dead code")+ Right thy -> normalizeTheory <$> addMessageDeductionRuleVariants thy+ where+ withLineNumbers err =+ unlines $ zipWith (\i l -> nr (show i) ++ l) [(1::Int)..] ls+ ++ ["", "Parse error when parsing the " ++ msg, err]+ where+ ls = lines str+ n = length (show (length ls))+ nr i = replicate (1 + max 0 (n - length i)) ' ' ++ i ++ ": "++-- | Create the test whether 'testParseFile' succeeds on all @*.spthy@ files+-- in a given directory and all its subdirectories of depth n.+testParseDirectory :: (FilePath -> Test) -- ^ Test creation function.+ -> Int -- ^ Maximal depth of traversal.+ -> FilePath -- ^ Starting directory.+ -> IO [Test]+testParseDirectory mkTest n dir+ | n < 0 = return []+ | otherwise = do+ rawContents <- getDirectoryContents dir+ let contents = [ dir </> content+ | content <- rawContents+ , content /= ".", content /= ".." ]+ subDirs <- filterM doesDirectoryExist contents+ innerTests <- mapM (testParseDirectory mkTest (n - 1)) subDirs+ let tests = [ file+ | file <- contents, takeExtension file == ".spthy" ]+ mapM_ (putStrLn . (" peparing: " ++)) tests+ return $ map mkTest tests ++ map TestList innerTests++
− src/Theory.hs
@@ -1,942 +0,0 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE TypeSynonymInstances #-}--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ Theory datatype and transformations on it.-module Theory (- -- * Axioms- Axiom(..)- , axName- , axFormula-- -- * Lemmas- , LemmaAttribute(..)- , TraceQuantifier(..)- , Lemma- , lName- , lTraceQuantifier- , lFormula- , lAttributes- , lProof- , unprovenLemma- , skeletonLemma-- -- * Theories- , Theory(..)- , TheoryItem(..)- , thyName- , thySignature- , thyCache- , thyItems- , theoryRules- , theoryLemmas- , theoryAxioms- , addAxiom- , addLemma- , removeLemma- , lookupLemma- , addComment- , addStringComment- , addFormalComment- , cprRuleE-- -- ** Open theories- , OpenTheory- , defaultOpenTheory- , addProtoRule- , applyPartialEvaluation- , addIntrRuleACs- , normalizeTheory-- -- ** Closed theories- , ClosedTheory- , ClosedRuleCache(..) -- FIXME: this is only exported for the Binary instances- , closeTheory- , openTheory-- , ClosedProtoRule(..)-- , getLemmas- , getIntrVariants- , getProtoRuleEs- , getProofContext- , getClassifiedRules- , getInjectiveFactInsts-- , getCaseDistinction-- -- ** Proving- , ProofSkeleton- , proveTheory-- -- ** Lemma references- , lookupLemmaProof- , modifyLemmaProof-- -- * Pretty printing- , prettyFormalComment- , prettyLemmaName- , prettyAxiom- , prettyLemma- , prettyClosedTheory- , prettyOpenTheory-- , prettyClosedSummary-- , prettyIntruderVariants- , prettyTraceQuantifier-- -- * Convenience exports- , module Theory.Model- , module Theory.Proof-- ) where--import Prelude hiding (id, (.))--import Data.Binary-import Data.DeriveTH-import Data.Foldable (Foldable, foldMap)-import Data.List-import Data.Maybe-import Data.Monoid (Sum(..))-import qualified Data.Set as S-import Data.Traversable (Traversable, traverse)--import Control.Basics-import Control.Category-import Control.DeepSeq-import Control.Monad.Reader-import qualified Control.Monad.State as MS-import Control.Parallel.Strategies--import Extension.Data.Label hiding (get)-import qualified Extension.Data.Label as L--import Theory.Model-import Theory.Proof-import Theory.Text.Pretty-import Theory.Tools.AbstractInterpretation-import Theory.Tools.InjectiveFactInstances-import Theory.Tools.LoopBreakers-import Theory.Tools.RuleVariants----------------------------------------------------------------------------------- Specific proof types----------------------------------------------------------------------------------- | Proof skeletons are used to represent proofs in open theories.-type ProofSkeleton = Proof ()---- | Convert a proof skeleton to an incremental proof without any sequent--- annotations.-skeletonToIncrementalProof :: ProofSkeleton -> IncrementalProof-skeletonToIncrementalProof = fmap (fmap (const Nothing))---- | Convert an incremental proof to a proof skeleton by dropping all--- annotations.-incrementalToSkeletonProof :: IncrementalProof -> ProofSkeleton-incrementalToSkeletonProof = fmap (fmap (const ()))------------------------------------------------------------------------------------ Commented sets of rewriting rules----------------------------------------------------------------------------------- | A protocol rewriting rule modulo E together with its possible assertion--- soundness proof.-type OpenProtoRule = ProtoRuleE---- | A closed proto rule lists its original rule modulo E, the corresponding--- variant modulo AC, and if required the assertion soundness proof.-data ClosedProtoRule = ClosedProtoRule- { _cprRuleE :: ProtoRuleE -- original rule modulo E- , _cprRuleAC :: ProtoRuleAC -- variant modulo AC- }- deriving( Eq, Ord, Show )--type OpenRuleCache = [IntrRuleAC]--data ClosedRuleCache = ClosedRuleCache- { _crcRules :: ClassifiedRules- , _crcUntypedCaseDists :: [CaseDistinction]- , _crcTypedCaseDists :: [CaseDistinction]- , _crcInjectiveFactInsts :: S.Set FactTag- }- deriving( Eq, Ord, Show )---$(mkLabels [''ClosedProtoRule, ''ClosedRuleCache])--instance HasRuleName ClosedProtoRule where- ruleName = ruleName . L.get cprRuleE----- Relation between open and closed rule sets-------------------------------------------------- | All intruder rules of a set of classified rules.-intruderRules :: ClassifiedRules -> [IntrRuleAC]-intruderRules rules = do- Rule (IntrInfo i) ps cs as <- joinAllRules rules- return $ Rule i ps cs as---- | Open a rule cache. Variants and precomputed case distinctions are dropped.-openRuleCache :: ClosedRuleCache -> OpenRuleCache-openRuleCache = intruderRules . L.get crcRules---- | Open a protocol rule; i.e., drop variants and proof annotations.-openProtoRule :: ClosedProtoRule -> OpenProtoRule-openProtoRule = L.get cprRuleE---- | Close a protocol rule; i.e., compute AC variant and typing assertion--- soundness sequent, if required.-closeProtoRule :: MaudeHandle -> OpenProtoRule -> ClosedProtoRule-closeProtoRule hnd ruE = ClosedProtoRule ruE (variantsProtoRule hnd ruE)--- | Close a rule cache. Hower, note that the--- requires case distinctions are not computed here.-closeRuleCache :: [LNGuarded] -- ^ Axioms to use.- -> [LNGuarded] -- ^ Typing lemmas to use.- -> SignatureWithMaude -- ^ Signature of theory.- -> [ClosedProtoRule] -- ^ Protocol rules with variants.- -> OpenRuleCache -- ^ Intruder rules modulo AC.- -> ClosedRuleCache -- ^ Cached rules and case distinctions.-closeRuleCache axioms typAsms sig protoRules intrRulesAC =- ClosedRuleCache- classifiedRules untypedCaseDists typedCaseDists injFactInstances- where- ctxt0 = ProofContext- sig classifiedRules injFactInstances UntypedCaseDist [] AvoidInduction- (error "closeRuleCache: trace quantifier should not matter here")-- -- inj fact instances- injFactInstances =- simpleInjectiveFactInstances $ L.get cprRuleE <$> protoRules-- -- precomputing the case distinctions: we make sure to only add safety- -- axioms. Otherwise, it wouldn't be sound to use the precomputed case- -- distinctions for properties proven using induction.- safetyAxioms = filter isSafetyFormula axioms- untypedCaseDists = precomputeCaseDistinctions ctxt0 safetyAxioms- typedCaseDists = refineWithTypingAsms typAsms ctxt0 untypedCaseDists-- -- classifying the rules- rulesAC = (fmap IntrInfo <$> intrRulesAC) <|>- ((fmap ProtoInfo . L.get cprRuleAC) <$> protoRules)-- anyOf ps = partition (\x -> any ($ x) ps)-- (nonProto, proto) = anyOf [isDestrRule, isConstrRule] rulesAC- (constr, destr) = anyOf [isConstrRule] nonProto-- -- and sort them into ClassifiedRules datastructure for later use in proofs- classifiedRules = ClassifiedRules- { _crConstruct = constr- , _crDestruct = destr- , _crProtocol = proto- }------------------------------------------------------------------------------------ Axioms (Trace filters)----------------------------------------------------------------------------------- | An axiom describes a property that must hold for all traces. Axioms are--- always used as lemmas in proofs.-data Axiom = Axiom- { _axName :: String- , _axFormula :: LNFormula- }- deriving( Eq, Ord, Show )--$(mkLabels [''Axiom])------------------------------------------------------------------------------------ Lemmas----------------------------------------------------------------------------------- | An attribute for a 'Lemma'.-data LemmaAttribute =- TypingLemma- | ReuseLemma- | InvariantLemma- deriving( Eq, Ord, Show )---- | A 'TraceQuantifier' stating whether we check satisfiability of validity.-data TraceQuantifier = ExistsTrace | AllTraces- deriving( Eq, Ord, Show )---- | A lemma describes a property that holds in the context of a theory--- together with a proof of its correctness.-data Lemma p = Lemma- { _lName :: String- , _lTraceQuantifier :: TraceQuantifier- , _lFormula :: LNFormula- , _lAttributes :: [LemmaAttribute]- , _lProof :: p- }- deriving( Eq, Ord, Show )--$(mkLabels [''Lemma])----- Instances---------------instance Functor Lemma where- fmap f (Lemma n qua fm atts prf) = Lemma n qua fm atts (f prf)--instance Foldable Lemma where- foldMap f = f . L.get lProof--instance Traversable Lemma where- traverse f (Lemma n qua fm atts prf) = Lemma n qua fm atts <$> f prf----- Lemma queries--------------------------------------- | Convert a trace quantifier to a sequent trace quantifier.-toSystemTraceQuantifier :: TraceQuantifier -> SystemTraceQuantifier-toSystemTraceQuantifier AllTraces = ExistsNoTrace-toSystemTraceQuantifier ExistsTrace = ExistsSomeTrace---- | True iff the lemma can be used as a typing lemma.-isTypingLemma :: Lemma p -> Bool-isTypingLemma lem =- (AllTraces == L.get lTraceQuantifier lem)- && (TypingLemma `elem` L.get lAttributes lem)----- Lemma construction/modification--------------------------------------- | Create a new unproven lemma from a formula modulo E.-unprovenLemma :: String -> [LemmaAttribute] -> TraceQuantifier -> LNFormula- -> Lemma ProofSkeleton-unprovenLemma name atts qua fm = Lemma name qua fm atts (unproven ())--skeletonLemma :: String -> [LemmaAttribute] -> TraceQuantifier -> LNFormula- -> ProofSkeleton -> Lemma ProofSkeleton-skeletonLemma name atts qua fm = Lemma name qua fm atts---- | The case-distinction kind allowed for a lemma-lemmaCaseDistKind :: Lemma p -> CaseDistKind-lemmaCaseDistKind lem- | TypingLemma `elem` L.get lAttributes lem = UntypedCaseDist- | otherwise = TypedCaseDist------------------------------------------------------------------------------------ Theories----------------------------------------------------------------------------------- | A formal comment is a header together with the body of the comment.-type FormalComment = (String, String)---- | A theory item built over the given rule type.-data TheoryItem r p =- RuleItem r- | LemmaItem (Lemma p)- | AxiomItem Axiom- | TextItem FormalComment- deriving( Show, Eq, Ord, Functor )----- | A theory contains a single set of rewriting rules modeling a protocol--- and the lemmas that-data Theory sig c r p = Theory {- _thyName :: String- , _thySignature :: sig- , _thyCache :: c- , _thyItems :: [TheoryItem r p]- }- deriving( Eq, Ord, Show )--$(mkLabels [''Theory])---- | Open theories can be extended. Invariants:--- 1. Lemma names are unique.-type OpenTheory =- Theory SignaturePure [IntrRuleAC] OpenProtoRule ProofSkeleton----- | Closed theories can be proven. Invariants:--- 1. Lemma names are unique--- 2. All proof steps with annotated sequents are sound with respect to the--- closed rule set of the theory.--- 3. Maude is running under the given handle.-type ClosedTheory =- Theory SignatureWithMaude ClosedRuleCache ClosedProtoRule IncrementalProof------ Shared theory modification functions-------------------------------------------- | Fold a theory item.-foldTheoryItem- :: (r -> a) -> (Axiom -> a) -> (Lemma p -> a) -> (FormalComment -> a)- -> TheoryItem r p -> a-foldTheoryItem fRule fAxiom fLemma fText i = case i of- RuleItem ru -> fRule ru- LemmaItem lem -> fLemma lem- TextItem txt -> fText txt- AxiomItem ax -> fAxiom ax---- | Map a theory item.-mapTheoryItem :: (r -> r') -> (p -> p') -> TheoryItem r p -> TheoryItem r' p'-mapTheoryItem f g =- foldTheoryItem (RuleItem . f) AxiomItem (LemmaItem . fmap g) TextItem---- | All rules of a theory.-theoryRules :: Theory sig c r p -> [r]-theoryRules =- foldTheoryItem return (const []) (const []) (const []) <=< L.get thyItems---- | All axioms of a theory.-theoryAxioms :: Theory sig c r p -> [Axiom]-theoryAxioms =- foldTheoryItem (const []) return (const []) (const []) <=< L.get thyItems---- | All lemmas of a theory.-theoryLemmas :: Theory sig c r p -> [Lemma p]-theoryLemmas =- foldTheoryItem (const []) (const []) return (const []) <=< L.get thyItems---- | Add a new axiom. Fails, if axiom with the same name exists.-addAxiom :: Axiom -> Theory sig c r p -> Maybe (Theory sig c r p)-addAxiom l thy = do- guard (isNothing $ lookupAxiom (L.get axName l) thy)- return $ modify thyItems (++ [AxiomItem l]) thy---- | Add a new lemma. Fails, if a lemma with the same name exists.-addLemma :: Lemma p -> Theory sig c r p -> Maybe (Theory sig c r p)-addLemma l thy = do- guard (isNothing $ lookupLemma (L.get lName l) thy)- return $ modify thyItems (++ [LemmaItem l]) thy---- | Remove a lemma by name. Fails, if the lemma does not exist.-removeLemma :: String -> Theory sig c r p -> Maybe (Theory sig c r p)-removeLemma lemmaName thy = do- _ <- lookupLemma lemmaName thy- return $ modify thyItems (concatMap fItem) thy- where- fItem = foldTheoryItem (return . RuleItem)- (return . AxiomItem)- check- (return . TextItem)- check l = do guard (L.get lName l /= lemmaName); return (LemmaItem l)---- | Find the axiom with the given name.-lookupAxiom :: String -> Theory sig c r p -> Maybe Axiom-lookupAxiom name = find ((name ==) . L.get axName) . theoryAxioms---- | Find the lemma with the given name.-lookupLemma :: String -> Theory sig c r p -> Maybe (Lemma p)-lookupLemma name = find ((name ==) . L.get lName) . theoryLemmas---- | Add a comment to the theory.-addComment :: Doc -> Theory sig c r p -> Theory sig c r p-addComment c = modify thyItems (++ [TextItem ("", render c)])---- | Add a comment represented as a string to the theory.-addStringComment :: String -> Theory sig c r p -> Theory sig c r p-addStringComment = addComment . vcat . map text . lines--addFormalComment :: FormalComment -> Theory sig c r p -> Theory sig c r p-addFormalComment c = modify thyItems (++ [TextItem c])------------------------------------------------------------------------------------ Open theory construction / modification----------------------------------------------------------------------------------- | Default theory-defaultOpenTheory :: OpenTheory-defaultOpenTheory = Theory "default" emptySignaturePure [] []---- | Open a theory by dropping the closed world assumption and values whose--- soundness dependens on it.-openTheory :: ClosedTheory -> OpenTheory-openTheory (Theory n sig c items) =- Theory n (toSignaturePure sig) (openRuleCache c)- (map (mapTheoryItem openProtoRule incrementalToSkeletonProof) items)---- | Find the open protocol rule with the given name.-lookupOpenProtoRule :: ProtoRuleName -> OpenTheory -> Maybe OpenProtoRule-lookupOpenProtoRule name =- find ((name ==) . L.get rInfo) . theoryRules---- | Add a new protocol rules. Fails, if a protocol rule with the same name--- exists.-addProtoRule :: ProtoRuleE -> OpenTheory -> Maybe OpenTheory-addProtoRule ruE thy = do- guard (maybe True ((ruE ==)) $- lookupOpenProtoRule (L.get rInfo ruE) thy)- return $ modify thyItems (++ [RuleItem ruE]) thy---- | Add intruder proof rules.-addIntrRuleACs :: [IntrRuleAC] -> OpenTheory -> OpenTheory-addIntrRuleACs rs' = modify (thyCache) (\rs -> nub $ rs ++ rs')---- | Normalize the theory representation such that they remain semantically--- equivalent. Use this function when you want to compare two theories (quite--- strictly) for semantic equality; e.g., when testing the parser.-normalizeTheory :: OpenTheory -> OpenTheory-normalizeTheory =- L.modify thyCache sort- . L.modify thyItems (\items -> do- item <- items- return $ case item of- LemmaItem lem ->- LemmaItem $ L.modify lProof stripProofAnnotations $ lem- RuleItem _ -> item- TextItem _ -> item- AxiomItem _ -> item)- where- stripProofAnnotations :: ProofSkeleton -> ProofSkeleton- stripProofAnnotations = fmap stripProofStepAnnotations- stripProofStepAnnotations (ProofStep method ()) =- ProofStep (case method of- Sorry _ -> Sorry Nothing- Contradiction _ -> Contradiction Nothing- _ -> method)- ()------------------------------------------------------------------------------------ Closed theory querying / construction / modification----------------------------------------------------------------------------------- querying---------------- | All lemmas.-getLemmas :: ClosedTheory -> [Lemma IncrementalProof]-getLemmas = theoryLemmas---- | The variants of the intruder rules.-getIntrVariants :: ClosedTheory -> [IntrRuleAC]-getIntrVariants = intruderRules . L.get (crcRules . thyCache)---- | All protocol rules modulo E.-getProtoRuleEs :: ClosedTheory -> [ProtoRuleE]-getProtoRuleEs = map openProtoRule . theoryRules---- | Get the proof context for a lemma of the closed theory.-getProofContext :: Lemma a -> ClosedTheory -> ProofContext-getProofContext l thy = ProofContext- ( L.get thySignature thy)- ( L.get (crcRules . thyCache) thy)- ( L.get (crcInjectiveFactInsts . thyCache) thy)- kind- ( L.get (cases . thyCache) thy)- inductionHint- (toSystemTraceQuantifier $ L.get lTraceQuantifier l)- where- kind = lemmaCaseDistKind l- cases = case kind of UntypedCaseDist -> crcUntypedCaseDists- TypedCaseDist -> crcTypedCaseDists- inductionHint- | any (`elem` [TypingLemma, InvariantLemma]) (L.get lAttributes l) = UseInduction- | otherwise = AvoidInduction---- | The facts with injective instances in this theory-getInjectiveFactInsts :: ClosedTheory -> S.Set FactTag-getInjectiveFactInsts = L.get (crcInjectiveFactInsts . thyCache)---- | The classified set of rules modulo AC in this theory.-getClassifiedRules :: ClosedTheory -> ClassifiedRules-getClassifiedRules = L.get (crcRules . thyCache)---- | The precomputed case distinctions.-getCaseDistinction :: CaseDistKind -> ClosedTheory -> [CaseDistinction]-getCaseDistinction UntypedCaseDist = L.get (crcUntypedCaseDists . thyCache)-getCaseDistinction TypedCaseDist = L.get (crcTypedCaseDists . thyCache)----- construction-------------------- -- | Convert a lemma to the corresponding guarded formula.--- lemmaToGuarded :: Lemma p -> Maybe LNGuarded--- lemmaToGuarded lem =---- | Close a theory by closing its associated rule set and checking the proof--- skeletons and caching AC variants as well as precomputed case distinctions.------ This function initializes the relation to the Maude process with the--- correct signature. This is the right place to do that because in a closed--- theory the signature may not change any longer.-closeTheory :: FilePath -- ^ Path to the Maude executable.- -> OpenTheory- -> IO ClosedTheory-closeTheory maudePath thy0 = do- sig <- toSignatureWithMaude maudePath $ L.get thySignature thy0- return $ closeTheoryWithMaude sig thy0---- | Close a theory given a maude signature. This signature must be valid for--- the given theory.-closeTheoryWithMaude :: SignatureWithMaude -> OpenTheory -> ClosedTheory-closeTheoryWithMaude sig thy0 = do- proveTheory checkProof $ Theory (L.get thyName thy0) sig cache items- where- cache = closeRuleCache axioms typAsms sig rules (L.get thyCache thy0)- checkProof = checkAndExtendProver (sorryProver Nothing)-- -- Maude / Signature handle- hnd = L.get sigmMaudeHandle sig-- -- Close all theory items: in parallel (especially useful for variants)- --- -- NOTE that 'rdeepseq' is OK here, as the proof has not yet been checked- -- and therefore no constraint systems will be unnecessarily cached.- (items, _solveRel, _breakers) = (`runReader` hnd) $ addSolvingLoopBreakers- ((closeTheoryItem <$> L.get thyItems thy0) `using` parList rdeepseq)- closeTheoryItem = foldTheoryItem- (RuleItem . closeProtoRule hnd)- AxiomItem- (LemmaItem . fmap skeletonToIncrementalProof)- TextItem-- -- extract typing axioms and lemmas- axioms = do AxiomItem ax <- items- return $ formulaToGuarded_ $ L.get axFormula ax- typAsms = do LemmaItem lem <- items- guard (isTypingLemma lem)- return $ formulaToGuarded_ $ L.get lFormula lem-- -- extract protocol rules- rules = theoryRules (Theory errClose errClose errClose items)- errClose = error "closeTheory"-- addSolvingLoopBreakers = useAutoLoopBreakersAC- (liftToItem $ enumPrems . L.get cprRuleAC)- (liftToItem $ enumConcs . L.get cprRuleAC)- (liftToItem $ getDisj . L.get (pracVariants . rInfo . cprRuleAC))- addBreakers- where- liftToItem f (RuleItem ru) = f ru- liftToItem _ _ = []-- addBreakers bs (RuleItem ru) =- RuleItem (L.set (pracLoopBreakers . rInfo . cprRuleAC) bs ru)- addBreakers _ item = item------ Partial evaluation / abstract interpretation---------------------------------------------------- | Apply partial evaluation.-applyPartialEvaluation :: EvaluationStyle -> ClosedTheory -> ClosedTheory-applyPartialEvaluation evalStyle thy0 =- closeTheoryWithMaude sig $- L.modify thyItems replaceProtoRules (openTheory thy0)- where- sig = L.get thySignature thy0- ruEs = getProtoRuleEs thy0- (st', ruEs') = (`runReader` L.get sigmMaudeHandle sig) $- partialEvaluation evalStyle ruEs-- replaceProtoRules [] = []- replaceProtoRules (item:items)- | isRuleItem item =- [ TextItem ("text", render ppAbsState)-- ] ++ map RuleItem ruEs' ++ filter (not . isRuleItem) items- | otherwise = item : replaceProtoRules items-- isRuleItem (RuleItem _) = True- isRuleItem _ = False-- ppAbsState =- (text $ " the abstract state after partial evaluation"- ++ " contains " ++ show (S.size st') ++ " facts:") $--$- (numbered' $ map prettyLNFact $ S.toList st') $--$- (text $ "This abstract state results in " ++ show (length ruEs') ++- " refined multiset rewriting rules.\n" ++- "Note that the original number of multiset rewriting rules was "- ++ show (length ruEs) ++ ".\n\n")---- Applying provers------------------------ | Prove both the assertion soundness as well as all lemmas of the theory. If--- the prover fails on a lemma, then its proof remains unchanged.-proveTheory :: Prover -> ClosedTheory -> ClosedTheory-proveTheory prover thy =- modify thyItems ((`MS.evalState` []) . mapM prove) thy- where- prove item = case item of- LemmaItem l0 -> do l <- MS.gets (LemmaItem . proveLemma l0)- MS.modify (l :)- return l- _ -> do return item-- proveLemma lem preItems =- modify lProof add lem- where- ctxt = getProofContext lem thy- sys = mkSystem ctxt (theoryAxioms thy) preItems $ L.get lFormula lem- add prf = fromMaybe prf $ runProver prover ctxt 0 sys prf---- | Construct a constraint system for verifying the given formula.-mkSystem :: ProofContext -> [Axiom] -> [TheoryItem r p]- -> LNFormula -> System-mkSystem ctxt axioms previousItems =- -- Note that it is OK to add reusable lemmas directly to the system, as- -- they do not change the considered set of traces. This is the key- -- difference between lemmas and axioms.- addLemmas- . formulaToSystem (map (formulaToGuarded_ . L.get axFormula) axioms)- (L.get pcCaseDistKind ctxt)- (L.get pcTraceQuantifier ctxt)- where- addLemmas sys =- insertLemmas (gatherReusableLemmas $ L.get sCaseDistKind sys) sys-- gatherReusableLemmas kind = do- LemmaItem lem <- previousItems- guard $ lemmaCaseDistKind lem <= kind- && ReuseLemma `elem` L.get lAttributes lem- && AllTraces == L.get lTraceQuantifier lem- return $ formulaToGuarded_ $ L.get lFormula lem------------------------------------------------------------------------------------ References to lemmas----------------------------------------------------------------------------------- | Lemmas are referenced by their name.-type LemmaRef = String---- | Resolve a path in a theory.-lookupLemmaProof :: LemmaRef -> ClosedTheory -> Maybe IncrementalProof-lookupLemmaProof name thy = L.get lProof <$> lookupLemma name thy---- | Modify the proof at the given lemma ref, if there is one. Fails if the--- path is not present or if the prover fails.-modifyLemmaProof :: Prover -> LemmaRef -> ClosedTheory -> Maybe ClosedTheory-modifyLemmaProof prover name thy =- modA thyItems changeItems thy- where- findLemma (LemmaItem lem) = name == L.get lName lem- findLemma _ = False-- change preItems (LemmaItem lem) = do- let ctxt = getProofContext lem thy- sys = mkSystem ctxt (theoryAxioms thy) preItems $ L.get lFormula lem- lem' <- modA lProof (runProver prover ctxt 0 sys) lem- return $ LemmaItem lem'- change _ _ = error "LemmaProof: change: impossible"-- changeItems items = case break findLemma items of- (pre, i:post) -> do- i' <- change pre i- return $ pre ++ i':post- (_, []) -> Nothing------------------------------------------------------------------------------------ Pretty printing----------------------------------------------------------------------------------- | Pretty print a formal comment-prettyFormalComment :: HighlightDocument d => String -> String -> d-prettyFormalComment "" body = multiComment_ [body]-prettyFormalComment header body = text $ header ++ "{*" ++ body ++ "*}"---- | Pretty print a theory.-prettyTheory :: HighlightDocument d- => (sig -> d) -> (c -> d) -> (r -> d) -> (p -> d)- -> Theory sig c r p -> d-prettyTheory ppSig ppCache ppRule ppPrf thy = vsep $- [ kwTheoryHeader $ text $ L.get thyName thy- , lineComment_ "Function signature and definition of the equational theory E"- , ppSig $ L.get thySignature thy- , ppCache $ L.get thyCache thy- ] ++- parMap rdeepseq ppItem (L.get thyItems thy) ++- [ kwEnd ]- where- ppItem = foldTheoryItem- ppRule prettyAxiom (prettyLemma ppPrf) (uncurry prettyFormalComment)---- | Pretty print the lemma name together with its attributes.-prettyLemmaName :: HighlightDocument d => Lemma p -> d-prettyLemmaName l = case L.get lAttributes l of- [] -> text (L.get lName l)- as -> text (L.get lName l) <->- (brackets $ fsep $ punctuate comma $ map prettyLemmaAttribute as)- where- prettyLemmaAttribute TypingLemma = text "typing"- prettyLemmaAttribute ReuseLemma = text "reuse"- prettyLemmaAttribute InvariantLemma = text "use_induction"---- | Pretty print an axiom.-prettyAxiom :: HighlightDocument d => Axiom -> d-prettyAxiom ax =- kwAxiom <-> text (L.get axName ax) <> colon $-$- (nest 2 $ doubleQuotes $ prettyLNFormula $ L.get axFormula ax) $-$- (nest 2 $ if safety then lineComment_ "safety formula" else emptyDoc)- where- safety = isSafetyFormula $ formulaToGuarded_ $ L.get axFormula ax---- | Pretty print a lemma.-prettyLemma :: HighlightDocument d => (p -> d) -> Lemma p -> d-prettyLemma ppPrf lem =- kwLemma <-> prettyLemmaName lem <> colon $-$- (nest 2 $- sep [ prettyTraceQuantifier $ L.get lTraceQuantifier lem- , doubleQuotes $ prettyLNFormula $ L.get lFormula lem- ]- )- $-$- ppLNFormulaGuarded (L.get lFormula lem)- $-$- ppPrf (L.get lProof lem)- where- ppLNFormulaGuarded fm = case formulaToGuarded fm of- Left err -> multiComment $- text "conversion to guarded formula failed:" $$- nest 2 err- Right gf -> case toSystemTraceQuantifier $ L.get lTraceQuantifier lem of- ExistsNoTrace -> multiComment- ( text "guarded formula characterizing all counter-examples:" $-$- doubleQuotes (prettyGuarded (gnot gf)) )- ExistsSomeTrace -> multiComment- ( text "guarded formula characterizing all satisfying traces:" $-$- doubleQuotes (prettyGuarded gf) )----- | Pretty-print a non-empty bunch of intruder rules.-prettyIntruderVariants :: HighlightDocument d => [IntrRuleAC] -> d-prettyIntruderVariants vs = vcat . intersperse (text "") $ map prettyIntrRuleAC vs--{---- | Pretty-print the intruder variants section.-prettyIntrVariantsSection :: HighlightDocument d => [IntrRuleAC] -> d-prettyIntrVariantsSection rules =- prettyFormalComment "section" " Finite Variants of the Intruder Rules " $--$- nest 1 (prettyIntruderVariants rules)--}---- | Pretty print an open rule together with its assertion soundness proof.-prettyOpenProtoRule :: HighlightDocument d => OpenProtoRule -> d-prettyOpenProtoRule = prettyProtoRuleE--prettyIncrementalProof :: HighlightDocument d => IncrementalProof -> d-prettyIncrementalProof = prettyProofWith ppStep (const id)- where- ppStep step = sep- [ prettyProofMethod (psMethod step)- , if isNothing (psInfo step) then multiComment_ ["unannotated"]- else emptyDoc- ]---- | Pretty print an closed rule.-prettyClosedProtoRule :: HighlightDocument d => ClosedProtoRule -> d-prettyClosedProtoRule cru =- (prettyProtoRuleE ruE) $--$- (nest 2 $ prettyLoopBreakers (L.get rInfo ruAC) $-$ ppRuleAC)- where- ruAC = L.get cprRuleAC cru- ruE = L.get cprRuleE cru- ppRuleAC- | isTrivialProtoVariantAC ruAC ruE = multiComment_ ["has exactly the trivial AC variant"]- | otherwise = multiComment $ prettyProtoRuleAC ruAC---- | Pretty print an open theory.-prettyOpenTheory :: HighlightDocument d => OpenTheory -> d-prettyOpenTheory =- prettyTheory prettySignaturePure- (const emptyDoc) prettyOpenProtoRule prettyProof- -- prettyIntrVariantsSection prettyOpenProtoRule prettyProof---- | Pretty print a closed theory.-prettyClosedTheory :: HighlightDocument d => ClosedTheory -> d-prettyClosedTheory thy =- prettyTheory prettySignatureWithMaude- ppInjectiveFactInsts- -- (prettyIntrVariantsSection . intruderRules . L.get crcRules)- prettyClosedProtoRule- prettyIncrementalProof- thy- where- ppInjectiveFactInsts crc =- case S.toList $ L.get crcInjectiveFactInsts crc of- [] -> emptyDoc- tags -> lineComment $ sep- [ text "looping facts with injective instances:"- , nest 2 $ fsepList (text . showFactTagArity) tags ]--prettyClosedSummary :: Document d => ClosedTheory -> d-prettyClosedSummary thy =- vcat lemmaSummaries- where- lemmaSummaries = do- LemmaItem lem <- L.get thyItems thy- -- Note that here we are relying on the invariant that all proof steps- -- with a 'Just' annotation follow from the application of- -- 'execProofMethod' to their parent and are valid in the sense that- -- the application of 'execProofMethod' to their method and constraint- -- system is guaranteed to succeed.- --- -- This is guaranteed initially by 'closeTheory' and is (must be)- -- maintained by the provers being applied to the theory using- -- 'modifyLemmaProof' or 'proveTheory'. Note that we could check the- -- proof right before computing its status. This is however quite- -- expensive, as it requires recomputing all intermediate constraint- -- systems.- --- -- TODO: The whole consruction seems a bit hacky. Think of a more- -- principled constrution with better correctness guarantees.- let (status, Sum siz) = foldProof proofStepSummary $ L.get lProof lem- quantifier = (toSystemTraceQuantifier $ L.get lTraceQuantifier lem)- analysisType = parens $ prettyTraceQuantifier $ L.get lTraceQuantifier lem- return $ text (L.get lName lem) <-> analysisType <> colon <->- text (showProofStatus quantifier status) <->- parens (integer siz <-> text "steps")-- proofStepSummary = proofStepStatus &&& const (Sum (1::Integer))----- | Pretty print a 'TraceQuantifier'.-prettyTraceQuantifier :: Document d => TraceQuantifier -> d-prettyTraceQuantifier ExistsTrace = text "exists-trace"-prettyTraceQuantifier AllTraces = text "all-traces"----- Instances: FIXME: Sort them into the right files-----------------------------------------------------$( derive makeBinary ''TheoryItem)-$( derive makeBinary ''LemmaAttribute)-$( derive makeBinary ''TraceQuantifier)-$( derive makeBinary ''Axiom)-$( derive makeBinary ''Lemma)-$( derive makeBinary ''ClosedProtoRule)-$( derive makeBinary ''ClosedRuleCache)-$( derive makeBinary ''Theory)--$( derive makeNFData ''TheoryItem)-$( derive makeNFData ''LemmaAttribute)-$( derive makeNFData ''TraceQuantifier)-$( derive makeNFData ''Axiom)-$( derive makeNFData ''Lemma)-$( derive makeNFData ''ClosedProtoRule)-$( derive makeNFData ''ClosedRuleCache)-$( derive makeNFData ''Theory)-
− src/Theory/Constraint/Solver.hs
@@ -1,79 +0,0 @@--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ The public interface of the constraint solver, which implements all--- constraint reduction rules and together with a rule application heuristic.-module Theory.Constraint.Solver (-- -- * Constraint systems- module Theory.Constraint.System-- -- * Proof contexts- -- | The proof context captures all relevant information about the context- -- in which we are using the constraint solver. These are things like the- -- signature of the message theory, the multiset rewriting rules of the- -- protocol, the available precomputed case distinctions, whether induction- -- should be applied or not, whether typed or untyped case distinctions are- -- used, and whether we are looking for the existence of a trace or proving- -- the absence of any trace satisfying the constraint system.- , ProofContext(..)- , pcSignature- , pcRules- , pcCaseDists- , pcUseInduction- , pcCaseDistKind- , pcTraceQuantifier- , pcInjectiveFactInsts-- , InductionHint(..)-- , ClassifiedRules(..)- , joinAllRules- , crProtocol- , crConstruct- , crDestruct-- -- * Constraint reduction rules-- -- ** Contradictions- -- | All rules that reduce a constraint system to the empty set of- -- constraint systems. The 'Contradiction' datatype stores the information- -- about the contradiction for later display to the user.- , Contradiction- , contradictions-- -- ** Precomputed case distinctions- -- | For better speed, we precompute case distinctions. This is especially- -- important for getting rid of all chain constraints before actually- -- starting to verify security properties.- , CaseDistinction- , cdGoal- , cdCases-- , precomputeCaseDistinctions- , refineWithTypingAsms- , unsolvedChainConstraints-- -- * Proof methods- -- | Proof methods are the external to the constraint solver. They allow its- -- small step execution. This module also provides the heuristics for- -- selecting the best proof method to apply to a constraint system.- , module Theory.Constraint.Solver.ProofMethod-- -- ** Convenience export- , module Logic.Connectives-- ) where--import Logic.Connectives-import Theory.Constraint.Solver.CaseDistinctions-import Theory.Constraint.Solver.Contradictions-import Theory.Constraint.Solver.ProofMethod-import Theory.Constraint.Solver.Types-import Theory.Constraint.System--
− src/Theory/Constraint/Solver/CaseDistinctions.hs
@@ -1,318 +0,0 @@--- |--- Copyright : (c) 2011,2012 Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ Big-step proofs using case distinctions on the possible sources of a fact.-module Theory.Constraint.Solver.CaseDistinctions (- -- * Precomputed case distinctions-- -- ** Queries- unsolvedChainConstraints-- -- ** Construction- , precomputeCaseDistinctions- , refineWithTypingAsms-- -- ** Application- , solveWithCaseDistinction-- ) where--import Prelude hiding (id, (.))-import Safe--import Data.Foldable (asum)-import qualified Data.Map as M-import Data.Maybe (isJust)-import qualified Data.Set as S--import Control.Basics-import Control.Category-import Control.Monad.Disj-import Control.Monad.Reader-import Control.Monad.State (gets)-import Control.Parallel.Strategies--import Text.PrettyPrint.Highlight--import Extension.Data.Label-import Extension.Prelude--import Theory.Constraint.Solver.Contradictions (contradictorySystem)-import Theory.Constraint.Solver.Goals-import Theory.Constraint.Solver.Reduction-import Theory.Constraint.Solver.Simplify-import Theory.Constraint.Solver.Types-import Theory.Constraint.System-import Theory.Model------------------------------------------------------------------------------------ Precomputing case distinctions----------------------------------------------------------------------------------- | The number of remaining chain constraints of each case.-unsolvedChainConstraints :: CaseDistinction -> [Int]-unsolvedChainConstraints =- map (length . unsolvedChains . snd) . getDisj . get cdCases----- Construction-------------------- | The initial case distinction if the given goal is required and the--- given typing assumptions are justified.-initialCaseDistinction- :: ProofContext- -> [LNGuarded] -- ^ Axioms.- -> Goal- -> CaseDistinction-initialCaseDistinction ctxt axioms goal =- CaseDistinction goal cases- where- polish ((name, se), _) = ([name], se)- se0 = insertLemmas axioms $ emptySystem UntypedCaseDist- cases = fmap polish $- runReduction instantiate ctxt se0 (avoid (goal, se0))- instantiate = do- insertGoal goal False- solveGoal goal---- | Refine a source case distinction by applying the additional proof step.-refineCaseDistinction- :: ProofContext- -> Reduction (a, [String]) -- proof step with result and path extension- -> CaseDistinction- -> ([a], CaseDistinction)-refineCaseDistinction ctxt proofStep th =- ( map fst $ getDisj refinement- , set cdCases (snd <$> refinement) th )- where- fs = avoid th- refinement = do- (names, se) <- get cdCases th- ((x, names'), se') <- fst <$> runReduction proofStep ctxt se fs- return (x, (combine names names', se'))-- -- Combine names such that the coerce rule is blended out.- combine [] ns' = ns'- combine ("coerce":ns) ns' = combine ns ns'- combine (n :_) _ = [n]---- | Solves all chain and splitting goals as well as all premise goals solvable--- with one of the given precomputed requires case distinction theorems, while--- repeatedly simplifying the proof state.------ Returns the names of the steps applied.-solveAllSafeGoals :: [CaseDistinction] -> Reduction [String]-solveAllSafeGoals ths =- solve []- where- safeGoal _ (_, (_, LoopBreaker)) = False- safeGoal doSplit (goal, _ ) =- case goal of- ChainG _ _ -> True- ActionG _ fa -> not (isKUFact fa)- PremiseG _ fa -> not (isKUFact fa)- DisjG _ -> doSplit- -- Uncomment to get more extensive case splitting- -- SplitG _ -> doSplit- SplitG _ -> False-- usefulGoal (_, (_, Useful)) = True- usefulGoal _ = False-- solve caseNames = do- simplifySystem- ctxt <- ask- contradictoryIf =<< gets (contradictorySystem ctxt)- goals <- gets openGoals- chains <- gets unsolvedChains- -- try to either solve a safe goal or use one of the precomputed case- -- distinctions- let noChainGoals = null [ () | (ChainG _ _, _) <- goals ]- -- we perform equation splits, if there is a chain goal starting- -- from a message variable; i.e., a chain constraint that is no- -- open goal.- splitAllowed = noChainGoals && not (null chains)- safeGoals = fst <$> filter (safeGoal splitAllowed) goals- usefulGoals = fst <$> filter usefulGoal goals- nextStep =- ((fmap return . solveGoal) <$> headMay safeGoals) <|>- (asum $ map (solveWithCaseDistinction ctxt ths) usefulGoals)- case nextStep of- Nothing -> return $ caseNames- Just step -> solve . (caseNames ++) =<< step------------------------------------------------------------------------------------ Applying precomputed case distinctions----------------------------------------------------------------------------------- | Match a precomputed 'CaseDistinction' to a goal.-matchToGoal- :: ProofContext -- ^ Proof context used for refining the case distinction.- -> CaseDistinction -- ^ Case distinction to use.- -> Goal -- ^ Goal to match- -> Maybe (Reduction [String])- -- ^ A constraint reduction step to apply the resulting case distinction.- -- Note that this step assumes that the theorem has been imported using- -- 'someInst' into the context that this reduction is executed in.- --- -- FIXME: This is a mess. Factor code such that this inter-dependency- -- between 'applyCaseDistinction' and 'matchToGoal' goes away.-matchToGoal ctxt th goalTerm =- case (goalTerm, get cdGoal th) of- ( PremiseG (iTerm, premIdxTerm) faTerm- ,PremiseG pPat@(iPat, _ ) faPat ) ->- let match = faTerm `matchFact` faPat <> iTerm `matchLVar` iPat in- case runReader (solveMatchLNTerm match) (get pcMaudeHandle ctxt) of- [] -> Nothing- subst:_ -> Just $ genericApply subst $- -- add the missing edge to each case of the theorem- modify sEdges (substNodePrem pPat (iPat, premIdxTerm))-- (ActionG iTerm faTerm, ActionG iPat faPat) ->- let match = faTerm `matchFact` faPat <> iTerm `matchLVar` iPat in- case runReader (solveMatchLNTerm match) (get pcMaudeHandle ctxt) of- [] -> Nothing- subst:_ -> Just $ genericApply subst id-- -- No other matches possible, as we only precompute case distinctions for- -- premises and KU-actions.- _ -> Nothing- where- genericApply subst systemModifier = do- void (solveSubstEqs SplitNow subst)- (names, sysTh) <- disjunctionOfList $ getDisj $ get cdCases th- conjoinSystem (systemModifier sysTh)- return names-- substNodePrem from to = S.map- (\ e@(Edge c p) -> if p == from then Edge c to else e)---- | Try to solve a premise goal or 'Ded' action using the first precomputed--- case distinction with a matching premise.-solveWithCaseDistinction :: ProofContext- -> [CaseDistinction]- -> Goal- -> Maybe (Reduction [String])-solveWithCaseDistinction hnd ths goal = do- -- goal <- toBigStepGoal goal0- asum [ applyCaseDistinction hnd th goal | th <- ths ]---- | Apply a precomputed case distinction theorem to a required fact.-applyCaseDistinction :: ProofContext- -> CaseDistinction -- ^ Case distinction theorem.- -> Goal -- ^ Required goal- -> Maybe (Reduction [String])-applyCaseDistinction ctxt th goal- | isJust $ matchToGoal ctxt th goal = Just $ do- markGoalAsSolved "precomputed" goal- thRenamed <- rename th- fromJustNote "applyCaseDistinction: impossible" $- matchToGoal ctxt thRenamed goal-- | otherwise = Nothing---- | Saturate the case distinctions with respect to each other such that no--- additional splitting is introduced; i.e., only rules with a single or no--- conclusion are used for the saturation.-saturateCaseDistinctions- :: ProofContext -> [CaseDistinction] -> [CaseDistinction]-saturateCaseDistinctions ctxt =- go- where- go ths =- if any or (changes `using` parList rdeepseq)- then go ths'- else ths'- where- (changes, ths') = unzip $ map (refineCaseDistinction ctxt solver) ths- goodTh th = length (getDisj (get cdCases th)) <= 1- solver = do names <- solveAllSafeGoals (filter goodTh ths)- return (not $ null names, names)---- | Precompute a saturated set of case distinctions.-precomputeCaseDistinctions- :: ProofContext- -> [LNGuarded] -- ^ Axioms.- -> [CaseDistinction]-precomputeCaseDistinctions ctxt axioms =- map cleanupCaseNames $ saturateCaseDistinctions ctxt rawCaseDists- where- cleanupCaseNames = modify cdCases $ fmap $ first $- filter (not . null)- . map (filter (`elem` '_' : ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9']))-- rawCaseDists =- initialCaseDistinction ctxt axioms <$> (protoGoals ++ msgGoals)-- -- construct case distinction starting from facts from non-special rules- protoGoals = someProtoGoal <$> absProtoFacts- msgGoals = someKUGoal <$> absMsgFacts-- getProtoFact (Fact KUFact _ ) = mzero- getProtoFact (Fact KDFact _ ) = mzero- getProtoFact fa = return fa-- absFact (Fact tag ts) = (tag, length ts)-- nMsgVars n = [ varTerm (LVar "t" LSortMsg i) | i <- [1..fromIntegral n] ]-- someProtoGoal :: (FactTag, Int) -> Goal- someProtoGoal (tag, arity) =- PremiseG (someNodeId, PremIdx 0) (Fact tag (nMsgVars arity))-- someKUGoal :: LNTerm -> Goal- someKUGoal m = ActionG someNodeId (kuFact m)-- someNodeId = LVar "i" LSortNode 0-- -- FIXME: Also use facts from proof context.- rules = get pcRules ctxt- absProtoFacts = sortednub $ do- ru <- joinAllRules rules- fa <- absFact <$> (getProtoFact =<< (get rConcs ru ++ get rPrems ru))- -- exclude facts handled specially by the prover- guard (not $ fst fa `elem` [OutFact, InFact, FreshFact])- return fa-- absMsgFacts :: [LNTerm]- absMsgFacts = asum $ sortednub $- [ do return $ lit $ Var (LVar "t" LSortFresh 1)-- , [ fAppNonAC (s,k) $ nMsgVars k- | (s,k) <- S.toList . allFunctionSymbols . mhMaudeSig . get sigmMaudeHandle . get pcSignature $ ctxt- , (s,k) `S.notMember` implicitFunSig, k > 0 ]- ]---- | Refine a set of case distinction by exploiting additional typing--- assumptions.-refineWithTypingAsms- :: [LNGuarded] -- ^ Typing assumptions to use.- -> ProofContext -- ^ Proof context to use.- -> [CaseDistinction] -- ^ Original, untyped case distinctions.- -> [CaseDistinction] -- ^ Refined, typed case distinctions.-refineWithTypingAsms assumptions ctxt cases0 =- fmap (modifySystems removeFormulas) $- saturateCaseDistinctions ctxt $- modifySystems updateSystem <$> cases0- where- modifySystems = modify cdCases . fmap . second- updateSystem se =- modify sFormulas (S.union (S.fromList assumptions)) $- set sCaseDistKind TypedCaseDist $ se- removeFormulas =- modify sGoals (M.filterWithKey isNoDisjGoal)- . set sFormulas S.empty- . set sSolvedFormulas S.empty-- isNoDisjGoal (DisjG _) _ = False- isNoDisjGoal _ _ = True---
− src/Theory/Constraint/Solver/Contradictions.hs
@@ -1,242 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ This is the public interface for constructing and deconstructing constraint--- systems. The interface for performing constraint solving provided by--- "Theory.Constraint.Solver".-module Theory.Constraint.Solver.Contradictions (-- -- * Contradictory constraint systems- Contradiction(..)- , substCreatesNonNormalTerms- , contradictions- , contradictorySystem-- -- ** Pretty-printing- , prettyContradiction-- ) where--import Prelude hiding (id, (.))--import Data.Binary-import qualified Data.DAG.Simple as D (cyclic, reachableSet)-import Data.DeriveTH-import qualified Data.Foldable as F-import Data.List-import qualified Data.Map as M-import Data.Maybe (fromMaybe)-import Data.Monoid-import qualified Data.Set as S-import Safe (headMay)--import Control.Basics-import Control.Category-import Control.DeepSeq-import Control.Monad.Reader--import qualified Extension.Data.Label as L-import Extension.Prelude--import Theory.Constraint.Solver.Types-import Theory.Constraint.System-import Theory.Model-import Theory.Text.Pretty--import Term.Rewriting.Norm (maybeNotNfSubterms, nf')------------------------------------------------------------------------------------ Contradictions----------------------------------------------------------------------------------- | Reasons why a constraint 'System' can be contradictory.-data Contradiction =- Cyclic -- ^ The paths are cyclic.- | NonNormalTerms -- ^ Has terms that are not in normal form.- -- | NonLastNode -- ^ Has a non-silent node after the last node.- | ForbiddenExp -- ^ Forbidden Exp-down rule instance- | NonInjectiveFactInstance (NodeId, NodeId, NodeId)- -- ^ Contradicts that certain facts have unique instances.- | IncompatibleEqs -- ^ Incompatible equalities.- | FormulasFalse -- ^ False in formulas- | SuperfluousLearn LNTerm NodeId -- ^ A term is derived both before and after a learn- | NodeAfterLast (NodeId, NodeId) -- ^ There is a node after the last node.- deriving( Eq, Ord, Show )----- | 'True' if the constraint system is contradictory.-contradictorySystem :: ProofContext -> System -> Bool-contradictorySystem ctxt = not . null . contradictions ctxt---- | All CR-rules reducing a constraint system to *⟂* represented as a list of--- trivial contradictions. Note that some constraint systems are also removed--- because they have no unifier. This is part of unification. Note also that--- *S_{¬,@}* is handled as part of *S_∀*.-contradictions :: ProofContext -> System -> [Contradiction]-contradictions ctxt sys = F.asum- -- CR-rule **- [ guard (D.cyclic $ rawLessRel sys) *> pure Cyclic- -- CR-rule *N1*- , guard (hasNonNormalTerms sig sys) *> pure NonNormalTerms- -- CR-rule *N7*- , guard (hasForbiddenExp sys) *> pure ForbiddenExp- -- CR-rules *S_≐* and *S_≈* are implemented via the equation store- , guard (eqsIsFalse $ L.get sEqStore sys) *> pure IncompatibleEqs- -- CR-rules *S_⟂*, *S_{¬,last,1}*, *S_{¬,≐}*, *S_{¬,≈}*- , guard (S.member gfalse $ L.get sFormulas sys) *> pure FormulasFalse- ]- ++- -- This rule is not yet documented. It removes constraint systems that- -- require a unique fact to be present in the system state more than once.- -- Unique facts are declared as part of the specification of the rule- -- system.- (NonInjectiveFactInstance <$> nonInjectiveFactInstances ctxt sys)- ++- -- TODO: Document corresponding constratint reduction rule.- (NodeAfterLast <$> nodesAfterLast sys)- where- sig = L.get pcSignature ctxt---- | True iff there are terms in the node constraints that are not in normal form wrt.--- to 'Term.Rewriting.Norm.norm' (DH/AC).-hasNonNormalTerms :: SignatureWithMaude -> System -> Bool-hasNonNormalTerms sig se =- any (not . (`runReader` hnd) . nf') (maybeNonNormalTerms hnd se)- where hnd = L.get sigmMaudeHandle sig---- | Returns all (sub)terms of node constraints that may be not in normal form.-maybeNonNormalTerms :: MaudeHandle -> System -> [LNTerm]-maybeNonNormalTerms hnd se =- sortednub . concatMap getTerms . M.elems . L.get sNodes $ se- where getTerms (Rule _ ps cs as) = do- f <- ps++cs++as- t <- factTerms f- maybeNotNfSubterms (mhMaudeSig hnd) t--substCreatesNonNormalTerms :: MaudeHandle -> System -> LNSubstVFresh -> Bool-substCreatesNonNormalTerms hnd se =- \subst -> any (not . nfApply subst) terms- where terms = maybeNonNormalTerms hnd se- nfApply subst0 t = t == t' || nf' t' `runReader` hnd- where tvars = freesList t- subst = restrictVFresh tvars subst0- t' = apply (freshToFreeAvoidingFast subst tvars) t---- | True if there is no @EXP-down@ rule that should be replaced by an--- @EXP-up@ rule.-hasForbiddenExp :: System -> Bool-hasForbiddenExp se =- any (isForbiddenExp) $ M.elems $ L.get sNodes se---- | @isForbiddenExp ru@ returns @True@ if @ru@ is not allowed in--- a normal dependency graph.--- > isForbiddenExp (Rule () [undefined, Fact KUFact [undefined, Mult (Inv x1) x2]]--- [Fact KDFact [expTagToTerm IsExp, Exp p1 (Mult x2 x3)]] [])--- > False--- > isForbiddenExp (Rule () [undefined, Fact KUFact [undefined, Mult (Inv x1) x2]]--- [Fact KDFact [expTagToTerm IsExp, Exp p1 x2]] [])--- > True-isForbiddenExp :: Rule a -> Bool-isForbiddenExp ru = fromMaybe False $ do- [p1,p2] <- return $ L.get rPrems ru- [conc] <- return $ L.get rConcs ru- (DnK, viewTerm2 -> FExp _ _) <- kFactView p1- (UpK, b ) <- kFactView p2- (DnK, viewTerm2 -> FExp g c) <- kFactView conc-- -- For a forbidden exp the following conditions must hold: g must be of- -- sort 'pub' and the required inputs for c are already required by b- return $ sortOfLNTerm g == LSortPub- && (inputTerms c \\ inputTerms b == [])----- | Compute all contradictions to injective fact instances.------ Formally, they are computed as follows. Let 'f' be a fact symbol with--- injective instances. Let i, j, and k be temporal variables ordered--- according to------ i < j < k------ and let there be an edge from (i,u) to (k,w) for some indices u and v------ Then, we have a contradiction if both the premise (k,w) that requires a--- fact 'f(t,...)' and there is a premise (j,v) requiring a fact 'f(t,...)'.------ These two premises would have to be merged, but cannot due to the ordering--- constraint 'j < k'.-nonInjectiveFactInstances :: ProofContext -> System -> [(NodeId, NodeId, NodeId)]-nonInjectiveFactInstances ctxt se = do- Edge c@(i, _) (k, _) <- S.toList $ L.get sEdges se- let kFaPrem = nodeConcFact c se- kTag = factTag kFaPrem- kTerm = firstTerm kFaPrem- conflictingFact fa = factTag fa == kTag && firstTerm fa == kTerm-- guard (kTag `S.member` L.get pcInjectiveFactInsts ctxt)- j <- S.toList $ D.reachableSet [i] less-- let isCounterExample = (j /= i) && (j /= k) &&- maybe False checkRule (M.lookup j $ L.get sNodes se)-- -- FIXME: There should be a weaker version of the rule that just- -- introduces the constraint 'k < j || k == j' here.- checkRule jRu = any conflictingFact (L.get rPrems jRu) &&- k `S.member` D.reachableSet [j] less-- guard isCounterExample- return (i, j, k) -- counter-example to unique fact instances- where- less = rawLessRel se- firstTerm = headMay . factTerms---- | The node-ids that must be instantiated to the trace, but are temporally--- after the last node.-nodesAfterLast :: System -> [(NodeId, NodeId)]-nodesAfterLast sys = case L.get sLastAtom sys of- Nothing -> []- Just i -> do j <- S.toList $ D.reachableSet [i] $ rawLessRel sys- guard (j /= i && isInTrace sys j)- return (i, j)----- | Pretty-print a 'Contradiction'.-prettyContradiction :: Document d => Contradiction -> d-prettyContradiction contra = case contra of- Cyclic -> text "cyclic"- IncompatibleEqs -> text "incompatible equalities"- NonNormalTerms -> text "non-normal terms"- ForbiddenExp -> text "non-normal exponentiation instance"- NonInjectiveFactInstance cex -> text $ "non-injective facts " ++ show cex- FormulasFalse -> text "from formulas"- SuperfluousLearn m v ->- doubleQuotes (prettyLNTerm m) <->- text ("derived before and after") <->- doubleQuotes (prettyNodeId v)- NodeAfterLast (i,j) ->- text $ "node " ++ show j ++ " after last node " ++ show i----- Instances---------------instance HasFrees Contradiction where- foldFrees f (SuperfluousLearn t v) = foldFrees f t `mappend` foldFrees f v- foldFrees f (NonInjectiveFactInstance x) = foldFrees f x- foldFrees f (NodeAfterLast x) = foldFrees f x- foldFrees _ _ = mempty-- mapFrees f (SuperfluousLearn t v) = SuperfluousLearn <$> mapFrees f t <*> mapFrees f v- mapFrees f (NonInjectiveFactInstance x) = NonInjectiveFactInstance <$> mapFrees f x- mapFrees f (NodeAfterLast x) = NodeAfterLast <$> mapFrees f x- mapFrees _ c = pure c--$( derive makeBinary ''Contradiction)-$( derive makeNFData ''Contradiction)
− src/Theory/Constraint/Solver/Goals.hs
@@ -1,284 +0,0 @@-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ The constraint reduction rules, which are not enforced as invariants in--- "Theory.Constraint.Solver.Reduction".------ A goal represents a possible application of a rule that may result in--- multiple cases or even non-termination (if applied repeatedly). These goals--- are computed as the list of 'openGoals'. See--- "Theory.Constraint.Solver.ProofMethod" for the public interface to solving--- goals and the implementation of heuristics.-module Theory.Constraint.Solver.Goals (- Usefulness(..)- , AnnotatedGoal- , openGoals- , solveGoal- ) where--import Prelude hiding (id, (.))--import qualified Data.DAG.Simple as D (cyclic)-import qualified Data.Map as M-import qualified Data.Set as S--import Control.Basics-import Control.Category-import Control.Monad.Disj-import Control.Monad.State (gets)--import Extension.Data.Label--import Theory.Constraint.Solver.Contradictions (substCreatesNonNormalTerms)-import Theory.Constraint.Solver.Reduction-import Theory.Constraint.Solver.Types-import Theory.Constraint.System-import Theory.Model------------------------------------------------------------------------------------ Extracting Goals---------------------------------------------------------------------------------data Usefulness =- Useful- -- ^ A goal that is likely to result in progress.- | LoopBreaker- -- ^ A goal that is delayed to avoid immediate termination. Needs to be- -- handled fairly.- | ProbablySolvable- -- ^ A goal that is very likely to be solvable without introducing further- -- interesting constraints. These goals are delayed until the very end.- deriving (Show, Eq)---- | Goals annotated with their number and usefulness.-type AnnotatedGoal = (Goal, (Integer, Usefulness))----- Instances----------------- We need a custom 'Ord' instance that guarantees that @Useful < Useless@.-instance Ord Usefulness where- compare a b =- check a b- where- check Useful Useful = EQ- check LoopBreaker LoopBreaker = EQ- check ProbablySolvable ProbablySolvable = EQ- check x y = compare (tag x) (tag y)-- tag (Useful) = 0 :: Int- tag (LoopBreaker) = 1- tag (ProbablySolvable) = 2----- | The list of goals that must be solved before a solution can be extracted.--- Each goal is annotated with its age and an indicator for its usefulness.-openGoals :: System -> [AnnotatedGoal]-openGoals sys = do- (goal, status) <- M.toList $ get sGoals sys- let solved = get gsSolved status- -- check whether the goal is still open- guard $ case goal of- ActionG _ (kFactView -> Just (UpK, m)) ->- not $ solved- || isMsgVar m || sortOfLNTerm m == LSortPub- -- handled by 'insertAction'- || isPair m || isInverse m || isProduct m- || isNullaryFunction m- ActionG _ _ -> not solved- PremiseG _ _ -> not solved- -- Technically the 'False' disj would be a solvable goal. However, we- -- have a separate proof method for this, i.e., contradictions.- DisjG (Disj []) -> False- DisjG _ -> not solved-- ChainG c _ ->- case kFactView (nodeConcFact c sys) of- Just (DnK, m) | isMsgVar m -> False- | otherwise -> not solved- fa -> error $ "openChainGoals: impossible fact: " ++ show fa-- -- FIXME: Split goals may be duplicated, we always have to check- -- explicitly if they still exist.- SplitG idx -> splitExists (get sEqStore sys) idx-- let- useful = case goal of- _ | get gsLoopBreaker status -> LoopBreaker- ActionG i (kFactView -> Just (UpK, m))- -- if there are KU-guards then all knowledge goals are useful- | hasKUGuards -> Useful- | isSimpleTerm m || deducible i m -> ProbablySolvable- _ -> Useful-- return (goal, (get gsNr status, useful))- where- existingDeps = rawLessRel sys- hasKUGuards =- any ((KUFact `elem`) . guardFactTags) $ S.toList $ get sFormulas sys-- -- We use the following heuristic for marking KU-actions as useful (worth- -- solving now) or useless (to be delayed until no more useful goals- -- remain). We ignore all goals that are simple terms or where there- -- exists a node, not after the premise or the last node, providing an Out- -- or KD conclusion that provides the message we are looking for as a- -- toplevel term. If such a node exist, then solving the goal will result- -- in at least one case where we didn't make real progress except.- toplevelTerms t@(destPair -> Just (t1, t2)) =- t : toplevelTerms t1 ++ toplevelTerms t2- toplevelTerms t@(destInverse -> Just t1) = t : toplevelTerms t1- toplevelTerms t = [t]-- deducible i m = or $ do- (j, ru) <- M.toList $ get sNodes sys- -- We cannot deduce a message from a last node.- guard (not $ isLast sys j)- let derivedMsgs = concatMap toplevelTerms $- [ t | Fact OutFact [t] <- get rConcs ru] <|>- [ t | Just (DnK, t) <- kFactView <$> get rConcs ru]- -- m is deducible from j without an immediate contradiction- -- if it is a derived message of 'ru' and the dependency does- -- not make the graph cyclic.- return $ m `elem` derivedMsgs &&- not (D.cyclic ((j, i) : existingDeps))------------------------------------------------------------------------------------ Solving 'Goal's----------------------------------------------------------------------------------- | @solveGoal rules goal@ enumerates all possible cases of how this goal--- could be solved in the context of the given @rules@. For each case, a--- sensible case-name is returned.-solveGoal :: Goal -> Reduction String-solveGoal goal = do- -- mark before solving, as representation might change due to unification- markGoalAsSolved "directly" goal- rules <- askM pcRules- case goal of- ActionG i fa -> solveAction (nonSilentRules rules) (i, fa)- PremiseG p fa ->- solvePremise (get crProtocol rules ++ get crConstruct rules) p fa- ChainG c p -> solveChain (get crDestruct rules) (c, p)- SplitG i -> solveSplit i- DisjG disj -> solveDisjunction disj---- The follwoing functions are internal to 'solveGoal'. Use them with great--- care.---- | CR-rule *S_at*: solve an action goal.-solveAction :: [RuleAC] -- ^ All rules labelled with an action- -> (NodeId, LNFact) -- ^ The action we are looking for.- -> Reduction String -- ^ A sensible case name.-solveAction rules (i, fa) = do- mayRu <- M.lookup i <$> getM sNodes- showRuleCaseName <$> case mayRu of- Nothing -> do ru <- labelNodeId i rules- act <- disjunctionOfList $ get rActs ru- void (solveFactEqs SplitNow [Equal fa act])- return ru-- Just ru -> do unless (fa `elem` get rActs ru) $ do- act <- disjunctionOfList $ get rActs ru- void (solveFactEqs SplitNow [Equal fa act])- return ru---- | CR-rules *DG_{2,P}* and *DG_{2,d}*: solve a premise with a direct edge--- from a unifying conclusion or using a destruction chain.------ Note that *In*, *Fr*, and *KU* facts are solved directly when adding a--- 'ruleNode'.----solvePremise :: [RuleAC] -- ^ All rules with a non-K-fact conclusion.- -> NodePrem -- ^ Premise to solve.- -> LNFact -- ^ Fact required at this premise.- -> Reduction String -- ^ Case name to use.-solvePremise rules p faPrem- | isKDFact faPrem = do- iLearn <- freshLVar "vl" LSortNode- mLearn <- varTerm <$> freshLVar "t" LSortMsg- let concLearn = kdFact mLearn- premLearn = outFact mLearn- -- !! Make sure that you construct the correct rule!- ruLearn = Rule (IntrInfo IRecvRule) [premLearn] [concLearn] []- cLearn = (iLearn, ConcIdx 0)- pLearn = (iLearn, PremIdx 0)- modM sNodes (M.insert iLearn ruLearn)- insertChain cLearn p- solvePremise rules pLearn premLearn-- | otherwise = do- (ru, c, faConc) <- insertFreshNodeConc rules- insertEdges [(c, faConc, faPrem, p)]- return $ showRuleCaseName ru---- | CR-rule *DG2_chain*: solve a chain constraint.-solveChain :: [RuleAC] -- ^ All destruction rules.- -> (NodeConc, NodePrem) -- ^ The chain to extend by one step.- -> Reduction String -- ^ Case name to use.-solveChain rules (c, p) = do- faConc <- gets $ nodeConcFact c- (do -- solve it by a direct edge- faPrem <- gets $ nodePremFact p- insertEdges [(c, faConc, faPrem, p)]- let m = case kFactView faConc of- Just (DnK, m') -> m'- _ -> error $ "solveChain: impossible"- caseName (viewTerm -> FApp o _) = showFunSymName o- caseName t = show t- return $ caseName m- `disjunction`- do -- extend it with one step- cRule <- gets $ nodeRule (nodeConcNode c)- (i, ru) <- insertFreshNode rules- -- contradicts normal form condition:- -- no edge from dexp to dexp KD premise- -- (this condition replaces the exp/noexp tags)- contradictoryIf (isDexpRule cRule && isDexpRule ru)- (v, faPrem) <- disjunctionOfList $ enumPrems ru- insertEdges [(c, faConc, faPrem, (i, v))]- markGoalAsSolved "directly" (PremiseG (i, v) faPrem)- insertChain (i, ConcIdx 0) p- return $ showRuleCaseName ru- )- where- isDexpRule ru = case get rInfo ru of- IntrInfo (DestrRule n) | n == expSymString -> True- _ -> False---- | Solve an equation split. There is no corresponding CR-rule in the rule--- system on paper because there we eagerly split over all variants of a rule.--- In practice, this is too expensive and we therefore use the equation store--- to delay these splits.-solveSplit :: SplitId -> Reduction String-solveSplit x = do- split <- gets ((`performSplit` x) . get sEqStore)- let errMsg = error "solveSplit: inexistent split-id"- store <- maybe errMsg disjunctionOfList split- -- FIXME: Simplify this interaction with the equation store- hnd <- getMaudeHandle- substCheck <- gets (substCreatesNonNormalTerms hnd)- store' <- simp hnd substCheck store- contradictoryIf (eqsIsFalse store')- sEqStore =: store'- return "split"---- | CR-rule *S_disj*: solve a disjunction of guarded formulas using a case--- distinction.------ In contrast to the paper, we use n-ary disjunctions and also split over all--- of them at once.-solveDisjunction :: Disj LNGuarded -> Reduction String-solveDisjunction disj = do- (i, gfm) <- disjunctionOfList $ zip [(1::Int)..] $ getDisj disj- insertFormula gfm- return $ "case_" ++ show i-
− src/Theory/Constraint/Solver/ProofMethod.hs
@@ -1,414 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2010-2012 Simon Meier & Benedikt Schmidt--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ Proof methods and heuristics: the external small-step interface to the--- constraint solver.-module Theory.Constraint.Solver.ProofMethod (- -- * Proof methods- CaseName- , ProofMethod(..)- , execProofMethod-- -- ** Heuristics- , GoalRanking(..)- , goalRankingName- , rankProofMethods-- , Heuristic- , roundRobinHeuristic- , useHeuristic-- -- ** Pretty Printing- , prettyProofMethod--) where--import Data.Binary-import Data.DeriveTH-import Data.Function (on)-import Data.Label hiding (get)-import qualified Data.Label as L-import Data.List-import qualified Data.Map as M-import Data.Monoid-import Data.Ord (comparing)-import qualified Data.Set as S-import Extension.Prelude (sortOn)--import Control.Basics-import Control.DeepSeq-import Control.Monad.Bind-import qualified Control.Monad.Trans.PreciseFresh as Precise--import Theory.Constraint.Solver.CaseDistinctions-import Theory.Constraint.Solver.Contradictions-import Theory.Constraint.Solver.Goals-import Theory.Constraint.Solver.Reduction-import Theory.Constraint.Solver.Simplify-import Theory.Constraint.Solver.Types-import Theory.Constraint.System-import Theory.Model-import Theory.Text.Pretty------------------------------------------------------------------------------------ Utilities----------------------------------------------------------------------------------- | @uniqueListBy eq changes xs@ zips the @changes@ with all sequences equal--- elements in the list.------ > uniqueListBy compare id (const [ (++ show i) | i <- [1..] ]) ["a","b","a"] =--- > ["a1","b","a2"]----uniqueListBy :: (a -> a -> Ordering) -> (a -> a) -> (Int -> [a -> a]) -> [a] -> [a]-uniqueListBy ord single distinguish xs0 =- map fst- $ sortBy (comparing snd)- $ concat $ map uniquify $ groupBy (\x y -> ord (fst x) (fst y) == EQ)- $ sortBy (ord `on` fst)- $ zip xs0 [(0::Int)..]- where- uniquify [] = error "impossible"- uniquify [(x,i)] = [(single x, i)]- uniquify xs = zipWith (\f (x,i) -> (f x, i)) dist xs- where- dist = distinguish $ length xs------------------------------------------------------------------------------------ Proof Methods----------------------------------------------------------------------------------- | Every case in a proof is uniquely named.-type CaseName = String---- | Sound transformations of sequents.-data ProofMethod =- Sorry (Maybe String) -- ^ Proof was not completed- | Solved -- ^ An attack was fond- | Simplify -- ^ A simplification step.- | SolveGoal Goal -- ^ A goal that was solved.- | Contradiction (Maybe Contradiction) -- ^ A contradiction could be- -- derived, possibly with a reason.- | Induction -- ^ Use inductive strengthening on- -- the single formula constraint in- -- the system.- deriving( Eq, Ord, Show )--instance HasFrees ProofMethod where- foldFrees f (SolveGoal g) = foldFrees f g- foldFrees f (Contradiction c) = foldFrees f c- foldFrees _ _ = mempty-- mapFrees f (SolveGoal g) = SolveGoal <$> mapFrees f g- mapFrees f (Contradiction c) = Contradiction <$> mapFrees f c- mapFrees _ method = pure method----- Proof method execution------------------------------- @execMethod rules method se@ checks first if the @method@ is applicable to--- the sequent @se@. Then, it applies the @method@ to the sequent under the--- assumption that the @rules@ describe all rewriting rules in scope.------ NOTE that the returned systems have their free substitution fully applied--- and all variable indices reset.-execProofMethod :: ProofContext- -> ProofMethod -> System -> Maybe (M.Map CaseName System)-execProofMethod ctxt method sys =- M.map cleanupSystem <$>- case method of- Sorry _ -> return M.empty- Solved- | null (openGoals sys) -> return M.empty- | otherwise -> Nothing- SolveGoal goal- | goal `M.member` L.get sGoals sys -> execSolveGoal goal- | otherwise -> Nothing- Simplify -> singleCase (/=) simplifySystem- Induction -> execInduction- Contradiction _- | null (contradictions ctxt sys) -> Nothing- | otherwise -> Just M.empty- where- -- at this point it is safe to remove the free substitution, as all- -- systems have it fully applied (by the virtue of a call to- -- simplifySystem). We also reset the variable indices here.- cleanupSystem =- (`Precise.evalFresh` Precise.nothingUsed)- . (`evalBindT` noBindings)- . someInst- . set sSubst emptySubst--- -- expect only one or no subcase in the given case distinction- singleCase check m =- case map fst $ getDisj $ execReduction m ctxt sys (avoid sys) of- [] -> return $ M.empty- [sys'] | check sys sys' -> return $ M.singleton "" sys'- | otherwise -> mzero- syss ->- return $ M.fromList (zip (map show [(1::Int)..]) syss)-- -- solve the given goal- -- PRE: Goal must be valid in this system.- execSolveGoal goal = do- return $ makeCaseNames $ map fst $ getDisj $- runReduction solver ctxt sys (avoid sys)- where- ths = L.get pcCaseDists ctxt- solver = do name <- maybe (solveGoal goal)- (fmap $ concat . intersperse "_")- (solveWithCaseDistinction ctxt ths goal)- simplifySystem- return name-- makeCaseNames =- M.fromListWith (error "case names not unique")- . uniqueListBy (comparing fst) id distinguish- where- distinguish n =- [ (\(x,y) -> (x ++ "_case_" ++ pad (show i), y))- | i <- [(1::Int)..] ]- where- l = length (show n)- pad cs = replicate (l - length cs) '0' ++ cs-- -- Apply induction: possible if the system contains only- -- a single, last-free, closed formula.- execInduction- | sys == sys0 =- case S.toList $ L.get sFormulas sys of- [gf] -> case ginduct gf of- Right (bc, sc) -> Just $ insCase "empty_trace" bc- $ insCase "non_empty_trace" sc- $ M.empty- _ -> Nothing- _ -> Nothing-- | otherwise = Nothing- where- sys0 = set sFormulas (L.get sFormulas sys)- $ set sLemmas (L.get sLemmas sys)- $ emptySystem (L.get sCaseDistKind sys)-- insCase name gf = M.insert name (set sFormulas (S.singleton gf) sys)----------------------------------------------------------------------------------- Heuristics----------------------------------------------------------------------------------- | The different available functions to rank goals with respect to their--- order of solving in a constraint system.-data GoalRanking =- GoalNrRanking- | UsefulGoalNrRanking- | SmartRanking Bool- deriving( Eq, Ord, Show )---- | The name/explanation of a 'GoalRanking'.-goalRankingName :: GoalRanking -> String-goalRankingName ranking =- "Goals sorted according to " ++ case ranking of- GoalNrRanking -> "their order of creation"- UsefulGoalNrRanking -> "their usefulness and order of creation"- SmartRanking useLoopBreakers -> smart useLoopBreakers- where- smart b = "the 'smart' heuristic (loop breakers " ++- (if b then "allowed" else "delayed") ++ ")."---- | Use a 'GoalRanking' to sort a list of 'AnnotatedGoal's stemming from the--- given constraint 'System'.-rankGoals :: GoalRanking -> System -> [AnnotatedGoal] -> [AnnotatedGoal]-rankGoals ranking = case ranking of- GoalNrRanking -> \_sys -> goalNrRanking- UsefulGoalNrRanking ->- \_sys -> sortOn (\(_, (nr, useless)) -> (useless, nr))- SmartRanking useLoopsBreakers -> smartRanking useLoopsBreakers---- | Use a 'GoalRanking' to generate the ranked, list of possible--- 'ProofMethod's and their corresponding results in this 'ProofContext' and--- for this 'System'. If the resulting list is empty, then the constraint--- system is solved.-rankProofMethods :: GoalRanking -> ProofContext -> System- -> [(ProofMethod, (M.Map CaseName System, String))]-rankProofMethods ranking ctxt sys = do- (m, expl) <-- (contradiction <$> contradictions ctxt sys)- <|> (case L.get pcUseInduction ctxt of- AvoidInduction -> [(Simplify, ""), (Induction, "")]- UseInduction -> [(Induction, ""), (Simplify, "")]- )- <|> (solveGoalMethod <$> (rankGoals ranking sys $ openGoals sys))- case execProofMethod ctxt m sys of- Just cases -> return (m, (cases, expl))- Nothing -> []- where- contradiction c = (Contradiction (Just c), "")- solveGoalMethod (goal, (nr, usefulness)) =- ( SolveGoal goal- , "nr. " ++ show nr ++ case usefulness of- Useful -> ""- LoopBreaker -> " (loop breaker)"- ProbablySolvable -> " (probably solvable)"- )--newtype Heuristic = Heuristic [GoalRanking]- deriving( Eq, Ord, Show )---- | Smart constructor for heuristics. Schedules the goal rankings in a--- round-robin fashion dependent on the proof depth.-roundRobinHeuristic :: [GoalRanking] -> Heuristic-roundRobinHeuristic = Heuristic---- | Use a heuristic to schedule a 'GoalRanking' according to the given--- proof-depth.-useHeuristic :: Heuristic -> Int -> GoalRanking-useHeuristic (Heuristic [] ) = error "useHeuristic: empty list of rankings"-useHeuristic (Heuristic rankings) =- ranking- where- n = length rankings-- ranking depth- | depth < 0 = error $ "useHeuristic: negative proof depth " ++ show depth- | otherwise = rankings !! (depth `mod` n)----{---- | Schedule the given local-heuristics in a round-robin fashion.-roundRobinHeuristic :: [GoalRanking] -> Heuristic-roundRobinHeuristic [] = error "roundRobin: empty list of rankings"-roundRobinHeuristic rankings =- methods- where- n = length rankings-- methods depth ctxt sys- | depth < 0 = error $ "roundRobin: negative proof depth " ++ show depth- | otherwise =- ( name- , ((Contradiction . Just) <$> contradictions ctxt sys)- <|> (case L.get pcUseInduction ctxt of- AvoidInduction -> [Simplify, Induction]- UseInduction -> [Induction, Simplify]- )- <|> ((SolveGoal . fst) <$> (ranking sys $ openGoals sys))- )- where- (name, ranking) = rankings !! (depth `mod` n)--}---- | Sort annotated goals according to their number.-goalNrRanking :: [AnnotatedGoal] -> [AnnotatedGoal]-goalNrRanking = sortOn (fst . snd)---- | A ranking function tuned for the automatic verification of--- classical security protocols that exhibit a well-founded protocol premise--- fact flow.-smartRanking :: Bool -- True if PremiseG loop-breakers should not be delayed- -> System- -> [AnnotatedGoal] -> [AnnotatedGoal]-smartRanking allowPremiseGLoopBreakers sys =- sortOnUsefulness . unmark . sortDecisionTree solveFirst . goalNrRanking- where- sortOnUsefulness = sortOn (snd . snd)-- unmark | allowPremiseGLoopBreakers = map unmarkPremiseG- | otherwise = id-- unmarkPremiseG (goal@(PremiseG _ _), (nr, _)) = (goal, (nr, Useful))- unmarkPremiseG annGoal = annGoal-- solveFirst =- [ isChainGoal . fst- , isDisjGoal . fst- , isNonLoopBreakerProtoFactGoal- , isStandardActionGoal . fst- , isFreshKnowsGoal . fst- , isSplitGoalSmall . fst- , isDoubleExpGoal . fst- , isNoLargeSplitGoal . fst ]- -- move the rest (mostly more expensive KU-goals) before expensive- -- equation splits-- -- FIXME: This small split goal preferral is quite hacky when using- -- induction. The problem is that we may end up solving message premise- -- goals all the time instead performing a necessary split. We should make- -- sure that a split does not get too old.- smallSplitGoalSize = 3-- isNonLoopBreakerProtoFactGoal (PremiseG _ fa, (_, Useful)) = not $ isKFact fa- isNonLoopBreakerProtoFactGoal _ = False-- msgPremise (ActionG _ fa) = do (UpK, m) <- kFactView fa; return m- msgPremise _ = Nothing-- isFreshKnowsGoal goal = case msgPremise goal of- Just (viewTerm -> Lit (Var lv)) | lvarSort lv == LSortFresh -> True- _ -> False-- isDoubleExpGoal goal = case msgPremise goal of- Just (viewTerm2 -> FExp _ (viewTerm2 -> FMult _)) -> True- _ -> False-- -- Be conservative on splits that don't exist.- isSplitGoalSmall (SplitG sid) =- maybe False (<= smallSplitGoalSize) $ splitSize (L.get sEqStore sys) sid- isSplitGoalSmall _ = False-- isNoLargeSplitGoal goal@(SplitG _) = isSplitGoalSmall goal- isNoLargeSplitGoal _ = True-- -- | @sortDecisionTree xs ps@ returns a reordering of @xs@- -- such that the sublist satisfying @ps!!0@ occurs first,- -- then the sublist satisfying @ps!!1@, and so on.- sortDecisionTree :: [a -> Bool] -> [a] -> [a]- sortDecisionTree [] xs = xs- sortDecisionTree (p:ps) xs = sat ++ sortDecisionTree ps nonsat- where (sat, nonsat) = partition p xs------------------------------------------------------------------------------------- Pretty printing----------------------------------------------------------------------------------- | Pretty-print a proof method.-prettyProofMethod :: HighlightDocument d => ProofMethod -> d-prettyProofMethod method = case method of- Solved -> keyword_ "SOLVED" <-> lineComment_ "trace found"- Induction -> keyword_ "induction"- Sorry reason ->- fsep [keyword_ "sorry", maybe emptyDoc lineComment_ reason]- SolveGoal goal ->- keyword_ "solve(" <-> prettyGoal goal <-> keyword_ ")"- Simplify -> keyword_ "simplify"- Contradiction reason ->- sep [ keyword_ "contradiction"- , maybe emptyDoc (lineComment . prettyContradiction) reason- ]------ Derived instances-----------------------$( derive makeBinary ''ProofMethod)-$( derive makeBinary ''GoalRanking)-$( derive makeBinary ''Heuristic)--$( derive makeNFData ''ProofMethod)-$( derive makeNFData ''GoalRanking)-$( derive makeNFData ''Heuristic)
− src/Theory/Constraint/Solver/Reduction.hs
@@ -1,665 +0,0 @@-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ A monad for writing constraint reduction steps together with basic steps--- for inserting nodes, edges, actions, and equations and applying--- substitutions.-module Theory.Constraint.Solver.Reduction (- -- * The constraint 'Reduction' monad- Reduction- , execReduction- , runReduction-- -- ** Change management- , ChangeIndicator(..)- , whenChanged- , applyChangeList- , whileChanging-- -- ** Accessing the 'ProofContext'- , getProofContext- , getMaudeHandle-- -- ** Inserting nodes, edges, and atoms- , labelNodeId- , insertFreshNode- , insertFreshNodeConc-- , insertGoal- , insertAtom- , insertEdges- , insertChain- , insertAction- , insertLess- , insertFormula- , reducibleFormula-- -- ** Goal management- , markGoalAsSolved- , removeSolvedSplitGoals-- -- ** Substitution application- , substSystem- , substNodes- , substEdges- , substLastAtom- , substLessAtoms- , substFormulas- , substSolvedFormulas-- -- ** Solving equalities- , SplitStrategy(..)-- , solveNodeIdEqs- , solveTermEqs- , solveFactEqs- , solveRuleEqs- , solveSubstEqs-- -- ** Conjunction with another constraint 'System'- , conjoinSystem-- -- ** Convenience export- , module Logic.Connectives-- ) where--import Debug.Trace-import Prelude hiding (id, (.))--import qualified Data.Foldable as F-import qualified Data.Map as M-import qualified Data.Set as S-import Data.List (mapAccumL)-import Safe--import Control.Basics-import Control.Category-import Control.Monad.Bind-import Control.Monad.Disj-import Control.Monad.Reader-import Control.Monad.State (StateT, execStateT, gets, runStateT)--import Text.PrettyPrint.Class--import Extension.Data.Label-import Extension.Data.Monoid (Monoid(..))-import Extension.Prelude--import Logic.Connectives--import Theory.Constraint.Solver.Contradictions-import Theory.Constraint.Solver.Types-import Theory.Constraint.System-import Theory.Model------------------------------------------------------------------------------------ The constraint reduction monad----------------------------------------------------------------------------------- | A constraint reduction step. Its state is the current constraint system,--- it can generate fresh names, split over cases, and access the proof--- context.-type Reduction = StateT System (FreshT (DisjT (Reader ProofContext)))----- Executing reductions---------------------------- | Run a constraint reduction. Returns a list of constraint systems whose--- combined solutions are equal to the solutions of the given system. This--- property is obviously not enforced, but it must be respected by all--- functions of type 'Reduction'.-runReduction :: Reduction a -> ProofContext -> System -> FreshState- -> Disj ((a, System), FreshState)-runReduction m ctxt se fs =- Disj $ (`runReader` ctxt) $ runDisjT $ (`runFreshT` fs) $ runStateT m se---- | Run a constraint reduction returning only the updated constraint systems--- and the new freshness states.-execReduction :: Reduction a -> ProofContext -> System -> FreshState- -> Disj (System, FreshState)-execReduction m ctxt se fs =- Disj $ (`runReader` ctxt) . runDisjT . (`runFreshT` fs) $ execStateT m se----- Change management------------------------- | Indicate whether the constraint system was changed or not.-data ChangeIndicator = Unchanged | Changed- deriving( Eq, Ord, Show )--instance Monoid ChangeIndicator where- mempty = Unchanged-- Changed `mappend` _ = Changed- _ `mappend` Changed = Changed- Unchanged `mappend` Unchanged = Unchanged---- | Return 'True' iff there was a change.-wasChanged :: ChangeIndicator -> Bool-wasChanged Changed = True-wasChanged Unchanged = False---- | Only apply a monadic action, if there has been a change.-whenChanged :: Monad m => ChangeIndicator -> m () -> m ()-whenChanged = when . wasChanged---- | Apply a list of changes to the proof state.-applyChangeList :: [Reduction ()] -> Reduction ChangeIndicator-applyChangeList [] = return Unchanged-applyChangeList changes = sequence_ changes >> return Changed---- | Execute a 'Reduction' as long as it results in changes. Indicate whether--- at least one change was performed.-whileChanging :: Reduction ChangeIndicator -> Reduction ChangeIndicator-whileChanging reduction =- go Unchanged- where- go indicator = do indicator' <- reduction- case indicator' of- Unchanged -> return indicator- Changed -> go indicator'----- Accessing the proof context----------------------------------- | Retrieve the 'ProofContext'.-getProofContext :: Reduction ProofContext-getProofContext = ask---- | Retrieve the 'MaudeHandle' from the 'ProofContext'.-getMaudeHandle :: Reduction MaudeHandle-getMaudeHandle = askM pcMaudeHandle----- Inserting (fresh) nodes into the constraint system---------------------------------------------------------- | Insert a fresh rule node labelled with a fresh instance of one of the--- rules and return one of the conclusions.-insertFreshNodeConc :: [RuleAC] -> Reduction (RuleACInst, NodeConc, LNFact)-insertFreshNodeConc rules = do- (i, ru) <- insertFreshNode rules- (v, fa) <- disjunctionOfList $ enumConcs ru- return (ru, (i, v), fa)---- | Insert a fresh rule node labelled with a fresh instance of one of the rules--- and solve it's 'Fr', 'In', and 'KU' premises immediatly.-insertFreshNode :: [RuleAC] -> Reduction (NodeId, RuleACInst)-insertFreshNode rules = do- i <- freshLVar "vr" LSortNode- (,) i <$> labelNodeId i rules---- | Label a node-id with a fresh instance of one of the rules and--- solve it's 'Fr', 'In', and 'KU' premises immediatly.------ PRE: Node must not yet be labelled with a rule.-labelNodeId :: NodeId -> [RuleAC] -> Reduction RuleACInst-labelNodeId = \i rules -> do- (ru, mrconstrs) <- importRule =<< disjunctionOfList rules- solveRuleConstraints mrconstrs- modM sNodes (M.insert i ru)- exploitPrems i ru- return ru- where- -- | Import a rule with all its variables renamed to fresh variables.- importRule ru = someRuleACInst ru `evalBindT` noBindings-- mkISendRuleAC m = return $ Rule (IntrInfo (ISendRule))- [kuFact m] [inFact m] [kLogFact m]--- mkFreshRuleAC m = Rule (ProtoInfo (ProtoRuleACInstInfo FreshRule []))- [] [freshFact m] []-- exploitPrems i ru = mapM_ (exploitPrem i ru) (enumPrems ru)-- exploitPrem i ru (v, fa) = case fa of- -- CR-rule *DG2_2* specialized for *In* facts.- Fact InFact [m] -> do- j <- freshLVar "vf" LSortNode- ruKnows <- mkISendRuleAC m- modM sNodes (M.insert j ruKnows)- modM sEdges (S.insert $ Edge (j, ConcIdx 0) (i, v))- exploitPrems j ruKnows-- -- CR-rule *DG2_2* specialized for *Fr* facts.- Fact FreshFact [m] -> do- j <- freshLVar "vf" LSortNode- modM sNodes (M.insert j (mkFreshRuleAC m))- unless (isFreshVar m) $ do- -- 'm' must be of sort fresh ==> enforce via unification- n <- varTerm <$> freshLVar "n" LSortFresh- void (solveTermEqs SplitNow [Equal m n])- modM sEdges (S.insert $ Edge (j, ConcIdx 0) (i,v))-- -- CR-rule *DG2_{2,u}*: solve a KU-premise by inserting the- -- corresponding KU-actions before this node.- _ | isKUFact fa -> do- j <- freshLVar "vk" LSortNode- insertLess j i- void (insertAction j fa)-- -- Store premise goal for later processing using CR-rule *DG2_2*- | otherwise -> insertGoal (PremiseG (i,v) fa) (v `elem` breakers)- where- breakers = ruleInfo (get praciLoopBreakers) (const []) $ get rInfo ru---- | Insert a chain constrain.-insertChain :: NodeConc -> NodePrem -> Reduction ()-insertChain c p = insertGoal (ChainG c p) False---- | Insert an edge constraint. CR-rule *DG1_2* is enforced automatically,--- i.e., the fact equalities are enforced.-insertEdges :: [(NodeConc, LNFact, LNFact, NodePrem)] -> Reduction ()-insertEdges edges = do- void (solveFactEqs SplitNow [ Equal fa1 fa2 | (_, fa1, fa2, _) <- edges ])- modM sEdges (\es -> foldr S.insert es [ Edge c p | (c,_,_,p) <- edges])---- | Insert an 'Action' atom. Ensures that (almost all) trivial *KU* actions--- are solved immediately using rule *S_{at,u,triv}*. We currently avoid--- adding intermediate products. Indicates whether nodes other than the given--- action have been added to the constraint system.------ FIXME: Ensure that intermediate products are also solved before stating--- that no rule is applicable.-insertAction :: NodeId -> LNFact -> Reduction ChangeIndicator-insertAction i fa = do- present <- (goal `M.member`) <$> getM sGoals- if present- then do return Unchanged- else do insertGoal goal False- case kFactView fa of- Just (UpK, viewTerm2 -> FPair m1 m2) ->- requiresKU m1 *> requiresKU m2 *> return Changed-- Just (UpK, viewTerm2 -> FInv m) ->- requiresKU m *> return Changed-- Just (UpK, viewTerm2 -> FMult ms) ->- mapM_ requiresKU ms *> return Changed-- _ -> return Unchanged- where- goal = ActionG i fa- -- Here we rely on the fact that the action is new. Otherwise, we might- -- loop due to generating new KU-nodes that are merged immediately.- requiresKU t = do- j <- freshLVar "vk" LSortNode- let faKU = kuFact t- insertLess j i- void (insertAction j faKU)---- | Insert a 'Less' atom. @insertLess i j@ means that *i < j* is added.-insertLess :: NodeId -> NodeId -> Reduction ()-insertLess i j = modM sLessAtoms (S.insert (i, j))---- | Insert a 'Last' atom and ensure their uniqueness.-insertLast :: NodeId -> Reduction ChangeIndicator-insertLast i = do- lst <- getM sLastAtom- case lst of- Nothing -> setM sLastAtom (Just i) >> return Unchanged- Just j -> solveNodeIdEqs [Equal i j]---- | Insert an atom. Returns 'Changed' if another part of the constraint--- system than the set of actions was changed.-insertAtom :: LNAtom -> Reduction ChangeIndicator-insertAtom ato = case ato of- EqE x y -> solveTermEqs SplitNow [Equal x y]- Action i fa -> insertAction (ltermNodeId' i) fa- Less i j -> do insertLess (ltermNodeId' i) (ltermNodeId' j)- return Unchanged- Last i -> insertLast (ltermNodeId' i)---- | Insert a 'Guarded' formula. Ensures that existentials, conjunctions, negated--- last atoms, and negated less atoms, are immediately solved using the rules--- *S_exists*, *S_and*, *S_not,last*, and *S_not,less*. Only the inserted--- formula is marked as solved. Other intermediate formulas are not marked.-insertFormula :: LNGuarded -> Reduction ()-insertFormula = do- insert True- where- insert mark fm = do- formulas <- getM sFormulas- solvedFormulas <- getM sSolvedFormulas- insert' mark formulas solvedFormulas fm-- insert' mark formulas solvedFormulas fm- | fm `S.member` formulas = return ()- | fm `S.member` solvedFormulas = return ()- | otherwise = case fm of- GAto ato -> do- markAsSolved- void (insertAtom (bvarToLVar ato))-- -- CR-rule *S_∧*- GConj fms -> do- markAsSolved- mapM_ (insert False) (getConj fms)-- -- Store for later applications of CR-rule *S_∨*- GDisj disj -> do- modM sFormulas (S.insert fm)- insertGoal (DisjG disj) False-- -- CR-rule *S_∃*- GGuarded Ex ss as gf -> do- -- must always mark as solved, as we otherwise may repeatedly- -- introduce fresh variables.- modM sSolvedFormulas $ S.insert fm- xs <- mapM (uncurry freshLVar) ss- let body = gconj (map GAto as ++ [gf])- insert False (substBound (zip [0..] (reverse xs)) body)-- -- CR-rule *S_{¬,⋖}*- GGuarded All [] [Less i j] gf | gf == gfalse -> do- markAsSolved- insert False (gdisj [GAto (EqE i j), GAto (Less j i)])-- -- CR-rule: FIXME add this rule to paper- GGuarded All [] [EqE i@(bltermNodeId -> Just _)- j@(bltermNodeId -> Just _) ] gf- | gf == gfalse -> do- markAsSolved- insert False (gdisj [GAto (Less i j), GAto (Less j i)])-- -- CR-rule *S_{¬,last}*- GGuarded All [] [Last i] gf | gf == gfalse -> do- markAsSolved- lst <- getM sLastAtom- j <- case lst of- Nothing -> do j <- freshLVar "last" LSortNode- void (insertLast j)- return (varTerm (Free j))- Just j -> return (varTerm (Free j))- insert False $ gdisj [ GAto (Less j i), GAto (Less i j) ]-- -- Guarded All quantification: store for saturation- GGuarded All _ _ _ -> modM sFormulas (S.insert fm)- where- markAsSolved = when mark $ modM sSolvedFormulas $ S.insert fm---- | 'True' iff the formula can be reduced by one of the rules implemented in--- 'insertFormula'.-reducibleFormula :: LNGuarded -> Bool-reducibleFormula fm = case fm of- GAto _ -> True- GConj _ -> True- GGuarded Ex _ _ _ -> True- GGuarded All [] [Less _ _] gf -> gf == gfalse- GGuarded All [] [Last _] gf -> gf == gfalse- _ -> False----- Goal management----------------------- | Combine the status of two goals.-combineGoalStatus :: GoalStatus -> GoalStatus -> GoalStatus-combineGoalStatus (GoalStatus solved1 age1 loops1)- (GoalStatus solved2 age2 loops2) =- GoalStatus (solved1 || solved2) (min age1 age2) (loops1 || loops2)---- | Insert a goal and its status with a new age. Merge status if goal exists.-insertGoalStatus :: Goal -> GoalStatus -> Reduction ()-insertGoalStatus goal status = do- age <- getM sNextGoalNr- modM sGoals $ M.insertWith' combineGoalStatus goal (set gsNr age status)- sNextGoalNr =: succ age---- | Insert a 'Goal' and store its age.-insertGoal :: Goal -> Bool -> Reduction ()-insertGoal goal looping = insertGoalStatus goal (GoalStatus False 0 looping)---- | Mark the given goal as solved.-markGoalAsSolved :: String -> Goal -> Reduction ()-markGoalAsSolved how goal =- case goal of- ActionG _ _ -> updateStatus- PremiseG _ fa- | isKDFact fa -> modM sGoals $ M.delete goal- | otherwise -> updateStatus- ChainG _ _ -> modM sGoals $ M.delete goal- SplitG _ -> updateStatus- DisjG disj -> modM sFormulas (S.delete $ GDisj disj) >>- modM sSolvedFormulas (S.insert $ GDisj disj) >>- updateStatus- where- updateStatus = do- mayStatus <- M.lookup goal <$> getM sGoals- case mayStatus of- Just status -> trace (msg status) $- modM sGoals $ M.insert goal $ set gsSolved True status- Nothing -> trace ("markGoalAsSolved: inexistent goal " ++ show goal) $ return ()-- msg status = render $ nest 2 $ fsep $- [ text ("solved goal nr. "++ show (get gsNr status))- <-> parens (text how) <> colon- , nest 2 (prettyGoal goal) ]--removeSolvedSplitGoals :: Reduction ()-removeSolvedSplitGoals = do- goals <- getM sGoals- existent <- splitExists <$> getM sEqStore- sequence_ [ modM sGoals $ M.delete goal- | goal@(SplitG i) <- M.keys goals, not (existent i) ]----- Substitution-------------------- | Apply the current substitution of the equation store to the remainder of--- the sequent.-substSystem :: Reduction ChangeIndicator-substSystem = do- c1 <- substNodes- substEdges- substLastAtom- substLessAtoms- substFormulas- substSolvedFormulas- substLemmas- c2 <- substGoals- substNextGoalNr- return (c1 <> c2)---- no invariants to maintain here-substEdges, substLessAtoms, substLastAtom, substFormulas,- substSolvedFormulas, substLemmas, substNextGoalNr :: Reduction ()--substEdges = substPart sEdges-substLessAtoms = substPart sLessAtoms-substLastAtom = substPart sLastAtom-substFormulas = substPart sFormulas-substSolvedFormulas = substPart sSolvedFormulas-substLemmas = substPart sLemmas-substNextGoalNr = return ()----- | Apply the current substitution of the equation store to a part of the--- sequent. This is an internal function.-substPart :: Apply a => (System :-> a) -> Reduction ()-substPart l = do subst <- getM sSubst- modM l (apply subst)---- | Apply the current substitution of the equation store the nodes of the--- constraint system. Indicates whether additional equalities were added to--- the equations store.-substNodes :: Reduction ChangeIndicator-substNodes =- substNodeIds <* ((modM sNodes . M.map . apply) =<< getM sSubst)---- | @setNodes nodes@ normalizes the @nodes@ such that node ids are unique and--- then updates the @sNodes@ field of the proof state to the corresponding map.--- Return @True@ iff new equalities have been added to the equation store.-setNodes :: [(NodeId, RuleACInst)] -> Reduction ChangeIndicator-setNodes nodes0 = do- sNodes =: M.fromList nodes- if null ruleEqs then return Unchanged- else solveRuleEqs SplitLater ruleEqs >> return Changed- where- -- merge nodes with equal node id- (ruleEqs, nodes) = first concat $ unzip $ map merge $ groupSortOn fst nodes0-- merge [] = unreachable "setNodes"- merge (keep:remove) = (map (Equal (snd keep) . snd) remove, keep)---- | Apply the current substitution of the equation store to the node ids and--- ensure uniqueness of the labels, as required by rule *U_lbl*. Indicates--- whether there where new equalities added to the equations store.-substNodeIds :: Reduction ChangeIndicator-substNodeIds =- whileChanging $ do- subst <- getM sSubst- nodes <- gets (map (first (apply subst)) . M.toList . get sNodes)- setNodes nodes---- | Substitute all goals. Keep the ones with the lower nr.-substGoals :: Reduction ChangeIndicator-substGoals = do- subst <- getM sSubst- goals <- M.toList <$> getM sGoals- sGoals =: M.empty- changes <- forM goals $ \(goal, status) -> case goal of- -- Look out for KU-actions that might need to be solved again.- ActionG i fa@(kFactView -> Just (UpK, m))- | (isMsgVar m || isProduct m) && (apply subst m /= m) ->- insertAction i (apply subst fa)- _ -> do modM sGoals $- M.insertWith' combineGoalStatus (apply subst goal) status- return Unchanged-- return (mconcat changes)----- Conjoining two constraint systems----------------------------------------- | @conjoinSystem se@ conjoins the logical information in @se@ to the--- constraint system. It assumes that the free variables in @se@ are shared--- with the free variables in the proof state.-conjoinSystem :: System -> Reduction ()-conjoinSystem sys = do- kind <- getM sCaseDistKind- unless (kind == get sCaseDistKind sys) $- error "conjoinSystem: typing-kind mismatch"- joinSets sSolvedFormulas- joinSets sLemmas- joinSets sEdges- F.mapM_ insertLast $ get sLastAtom sys- F.mapM_ (uncurry insertLess) $ get sLessAtoms sys- -- split-goals are not valid anymore- mapM_ (uncurry insertGoalStatus) $ filter (not . isSplitGoal . fst) $ M.toList $ get sGoals sys- F.mapM_ insertFormula $ get sFormulas sys- -- update nodes- _ <- (setNodes . (M.toList (get sNodes sys) ++) . M.toList) =<< getM sNodes- -- conjoin equation store- eqs <- getM sEqStore- let (eqs',splitIds) = (mapAccumL addDisj eqs (map snd . getConj $ get sConjDisjEqs sys))- setM sEqStore eqs'- -- add split-goals for all disjunctions of sys- mapM_ (`insertGoal` False) $ SplitG <$> splitIds- void (solveSubstEqs SplitNow $ get sSubst sys)- -- Propagate substitution changes. Ignore change indicator, as it is- -- assumed to be 'Changed' by default.- void substSystem- where- joinSets :: Ord a => (System :-> S.Set a) -> Reduction ()- joinSets proj = modM proj (`S.union` get proj sys)---- Unification via the equation store------------------------------------------ | 'SplitStrategy' denotes if the equation store should be split into--- multiple equation stores.-data SplitStrategy = SplitNow | SplitLater---- The 'ChangeIndicator' indicates whether at least one non-trivial equality--- was solved.---- | @noContradictoryEqStore@ suceeds iff the equation store is not--- contradictory.-noContradictoryEqStore :: Reduction ()-noContradictoryEqStore = (contradictoryIf . eqsIsFalse) =<< getM sEqStore---- | Add a list of term equalities to the equation store. And--- split resulting disjunction of equations according--- to given split strategy.------ Note that updating the remaining parts of the constraint system with the--- substitution has to be performed using a separate call to 'substSystem'.-solveTermEqs :: SplitStrategy -> [Equal LNTerm] -> Reduction ChangeIndicator-solveTermEqs splitStrat eqs0 =- case filter (not . evalEqual) eqs0 of- [] -> do return Unchanged- eqs1 -> do- hnd <- getMaudeHandle- se <- gets id- (eqs2, maySplitId) <- addEqs hnd eqs1 =<< getM sEqStore- setM sEqStore- =<< simp hnd (substCreatesNonNormalTerms hnd se)- =<< case (maySplitId, splitStrat) of- (Just splitId, SplitNow) -> disjunctionOfList- $ fromJustNote "solveTermEqs"- $ performSplit eqs2 splitId- (Just splitId, SplitLater) -> do- insertGoal (SplitG splitId) False- return eqs2- _ -> return eqs2- noContradictoryEqStore- return Changed---- | Add a list of equalities in substitution form to the equation store-solveSubstEqs :: SplitStrategy -> LNSubst -> Reduction ChangeIndicator-solveSubstEqs split subst =- solveTermEqs split [Equal (varTerm v) t | (v, t) <- substToList subst]---- | Add a list of node equalities to the equation store.-solveNodeIdEqs :: [Equal NodeId] -> Reduction ChangeIndicator-solveNodeIdEqs = solveTermEqs SplitNow . map (fmap varTerm)---- | Add a list of fact equalities to the equation store, if possible.-solveFactEqs :: SplitStrategy -> [Equal LNFact] -> Reduction ChangeIndicator-solveFactEqs split eqs = do- contradictoryIf (not $ all evalEqual $ map (fmap factTag) eqs)- solveListEqs (solveTermEqs split) $ map (fmap factTerms) eqs---- | Add a list of rule equalities to the equation store, if possible.-solveRuleEqs :: SplitStrategy -> [Equal RuleACInst] -> Reduction ChangeIndicator-solveRuleEqs split eqs = do- contradictoryIf (not $ all evalEqual $ map (fmap (get rInfo)) eqs)- solveListEqs (solveFactEqs split) $- map (fmap (get rConcs)) eqs ++ map (fmap (get rPrems)) eqs- ++ map (fmap (get rActs)) eqs---- | Solve a number of equalities between lists interpreted as free terms--- using the given solver for solving the entailed per-element equalities.-solveListEqs :: ([Equal a] -> Reduction b) -> [(Equal [a])] -> Reduction b-solveListEqs solver eqs = do- contradictoryIf (not $ all evalEqual $ map (fmap length) eqs)- solver $ concatMap flatten eqs- where- flatten (Equal l r) = zipWith Equal l r---- | Solve the constraints associated with a rule.-solveRuleConstraints :: Maybe RuleACConstrs -> Reduction ()-solveRuleConstraints (Just eqConstr) = do- hnd <- getMaudeHandle- (eqs, splitId) <- addRuleVariants eqConstr <$> getM sEqStore- insertGoal (SplitG splitId) False- -- do not use expensive substCreatesNonNormalTerms here- setM sEqStore =<< simp hnd (const False) eqs- noContradictoryEqStore-solveRuleConstraints Nothing = return ()-
− src/Theory/Constraint/Solver/Simplify.hs
@@ -1,456 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ This module implements all rules that do not result in case distinctions--- and equation solving. Some additional cases may although result from--- splitting over multiple AC-unifiers. Note that a few of these rules are--- implemented directly in the methods for inserting constraints to the--- constraint system. These methods are provided by--- "Theory.Constraint.Solver.Reduction".----module Theory.Constraint.Solver.Simplify (-- simplifySystem-- ) where--import Debug.Trace--import Prelude hiding (id, (.))--import qualified Data.DAG.Simple as D-import Data.Data-import Data.Either (partitionEithers)-import qualified Data.Foldable as F-import Data.List-import qualified Data.Map as M-import Data.Monoid (Monoid(..))-import qualified Data.Set as S--import Control.Basics-import Control.Category-import Control.Monad.Disj-import Control.Monad.Fresh-import Control.Monad.Reader-import Control.Monad.State (gets)---import Extension.Data.Label-import Extension.Prelude--import Theory.Constraint.Solver.Goals-import Theory.Constraint.Solver.Reduction-import Theory.Constraint.Solver.Types-import Theory.Constraint.System-import Theory.Model-import Theory.Text.Pretty----- | Apply CR-rules that don't result in case splitting until the constraint--- system does not change anymore.-simplifySystem :: Reduction ()-simplifySystem = do- -- Start simplification, indicating that some change happened- go (0 :: Int) [Changed]- -- Add all ordering constraint implied by CR-rule *N6*.- exploitUniqueMsgOrder- -- Remove equation split goals that do not exist anymore- removeSolvedSplitGoals- where- go n changes0- -- We stop as soon as all simplification steps have been run without- -- reporting any change to the constraint systemm.- | Unchanged == mconcat changes0 = return ()- | otherwise = do- -- Store original system for reporting- se0 <- gets id- -- Perform one initial substitution. We do not have to consider its- -- changes as 'substSystem' is idempotent.- void substSystem- -- Perform one simplification pass.- (c1,c2,c3) <- enforceNodeUniqueness- c4 <- enforceEdgeUniqueness- c5 <- solveUniqueActions- c6 <- reduceFormulas- c7 <- evalFormulaAtoms- c8 <- insertImpliedFormulas-- -- Report on looping behaviour if necessary- let changes = filter ((Changed ==) . snd) $- [ ("unique fresh instances (DG4)", c1)- , ("unique K↓-facts (N5↓)", c2)- , ("unique K↑-facts (N5↑)", c3)- , ("unique (linear) edges (DG2 and DG3)", c4)- , ("solve unambiguous actions (S_@)", c5)- , ("decompose trace formula", c6)- , ("propagate atom valuation to formula", c7)- , ("saturate under ∀-clauses (S_∀)", c8)- ]- traceIfLooping- | n <= 10 = id- | otherwise = trace $ render $ vsep- [ text "Simplifier iteration" <-> int n <> colon- , fsep $ text "The reduction-rules for" :- (punctuate comma $ map (text . fst) changes) ++- [text "were applied to the following constraint system."]- , nest 2 (prettySystem se0)- ]-- traceIfLooping $ go (n + 1) (map snd changes)----- | CR-rule *N6*: add ordering constraints between all KU-actions and--- KD-conclusions.-exploitUniqueMsgOrder :: Reduction ()-exploitUniqueMsgOrder = do- kdConcs <- gets (M.fromList . map (\(i, _, m) -> (m, i)) . allKDConcs)- kuActions <- gets (M.fromList . map (\(i, _, m) -> (m, i)) . allKUActions)- -- We can add all elements where we have an intersection- F.mapM_ (uncurry insertLess) $ M.intersectionWith (,) kdConcs kuActions---- | CR-rules *DG4*, *N5_u*, and *N5_d*: enforcing uniqueness of *Fresh* rule--- instances, *KU*-actions, and *KD*-conclusions.------ Returns 'Changed' if a change was done.-enforceNodeUniqueness :: Reduction (ChangeIndicator, ChangeIndicator, ChangeIndicator)-enforceNodeUniqueness =- (,,)- <$> (merge (const $ return Unchanged) freshRuleInsts)- <*> (merge (solveRuleEqs SplitNow) kdConcs)- <*> (merge (solveFactEqs SplitNow) kuActions)- where- -- *DG4*- freshRuleInsts se = do- (i, ru) <- M.toList $ get sNodes se- guard (isFreshRule ru)- return (ru, ((), i)) -- no need to merge equal rules-- -- *N5_d*- kdConcs sys = (\(i, ru, m) -> (m, (ru, i))) <$> allKDConcs sys-- -- *N5_u*- kuActions se = (\(i, fa, m) -> (m, (fa, i))) <$> allKUActions se-- merge :: Ord b- => ([Equal a] -> Reduction ChangeIndicator)- -- ^ Equation solver for 'Equal a'- -> (System -> [(b,(a,NodeId))])- -- ^ Candidate selector- -> Reduction ChangeIndicator --- merge solver candidates = do- changes <- gets (map mergers . groupSortOn fst . candidates)- mconcat <$> sequence changes- where- mergers [] = unreachable "enforceUniqueness"- mergers ((_,(xKeep, iKeep)):remove) =- mappend <$> solver (map (Equal xKeep . fst . snd) remove)- <*> solveNodeIdEqs (map (Equal iKeep . snd . snd) remove)----- | CR-rules *DG2_1* and *DG3*: merge multiple incoming edges to all facts--- and multiple outgoing edges from linear facts.-enforceEdgeUniqueness :: Reduction ChangeIndicator-enforceEdgeUniqueness = do- se <- gets id- let edges = S.toList (get sEdges se)- (<>) <$> mergeNodes eSrc eTgt edges- <*> mergeNodes eTgt eSrc (filter (proveLinearConc se . eSrc) edges)- where- -- | @proveLinearConc se (v,i)@ tries to prove that the @i@-th- -- conclusion of node @v@ is a linear fact.- proveLinearConc se (v, i) =- maybe False (isLinearFact . (get (rConc i))) $- M.lookup v $ get sNodes se-- -- merge the nodes on the 'mergeEnd' for edges that are equal on the- -- 'compareEnd'- mergeNodes mergeEnd compareEnd edges- | null eqs = return Unchanged- | otherwise = do- -- all indices of merged premises and conclusions must be equal- contradictoryIf (not $ and [snd l == snd r | Equal l r <- eqs])- -- nodes must be equal- solveNodeIdEqs $ map (fmap fst) eqs- where- eqs = concatMap (merge mergeEnd) $ groupSortOn compareEnd edges-- merge _ [] = error "exploitEdgeProps: impossible"- merge proj (keep:remove) = map (Equal (proj keep) . proj) remove---- | Special version of CR-rule *S_at*, which is only applied to solve actions--- that are guaranteed not to result in case splits.-solveUniqueActions :: Reduction ChangeIndicator-solveUniqueActions = do- rules <- nonSilentRules <$> askM pcRules- actionAtoms <- gets unsolvedActionAtoms-- -- FIXME: We might cache the result of this static computation in the- -- proof-context, e.g., in the 'ClassifiedRules'.- let uniqueActions = [ x | [x] <- group (sort ruleActions) ]- ruleActions = [ (tag, length ts)- | ru <- rules, Fact tag ts <- get rActs ru ]-- isUnique (Fact tag ts) = (tag, length ts) `elem` uniqueActions-- trySolve (i, fa)- | isUnique fa = solveGoal (ActionG i fa) >> return Changed- | otherwise = return Unchanged-- mconcat <$> mapM trySolve actionAtoms---- | Reduce all formulas as far as possible. See 'insertFormula' for the--- CR-rules exploited in this step. Note that this step is normally only--- required to decompose the formula in the initial constraint system.-reduceFormulas :: Reduction ChangeIndicator-reduceFormulas = do- formulas <- getM sFormulas- applyChangeList $ do- fm <- S.toList formulas- guard (reducibleFormula fm)- return $ do modM sFormulas $ S.delete fm- insertFormula fm---- | Try to simplify the atoms contained in the formulas. See--- 'partialAtomValuation' for an explanation of what CR-rules are exploited--- here.-evalFormulaAtoms :: Reduction ChangeIndicator-evalFormulaAtoms = do- ctxt <- ask- valuation <- gets (partialAtomValuation ctxt)- formulas <- getM sFormulas- applyChangeList $ do- fm <- S.toList formulas- case simplifyGuarded valuation fm of- Just fm' -> return $ do- case fm of- GDisj disj -> markGoalAsSolved "simplified" (DisjG disj)- _ -> return ()- modM sFormulas $ S.delete fm- modM sSolvedFormulas $ S.insert fm- insertFormula fm'- Nothing -> []---- | A partial valuation for atoms. The return value of this function is--- interpreted as follows.------ @partialAtomValuation ctxt sys ato == Just True@ if for every valuation--- @theta@ satisfying the graph constraints and all atoms in the constraint--- system @sys@, the atom @ato@ is also satisfied by @theta@.------ The interpretation for @Just False@ is analogous. @Nothing@ is used to--- represent *unknown*.----partialAtomValuation :: ProofContext -> System -> LNAtom -> Maybe Bool-partialAtomValuation ctxt sys =- eval- where- runMaude = (`runReader` get pcMaudeHandle ctxt)- before = alwaysBefore sys- lessRel = rawLessRel sys- nodesAfter = \i -> filter (i /=) $ S.toList $ D.reachableSet [i] lessRel-- -- | 'True' iff there in every solution to the system the two node-ids are- -- instantiated to a different index *in* the trace.- nonUnifiableNodes :: NodeId -> NodeId -> Bool- nonUnifiableNodes i j = maybe False (not . runMaude) $- (unifiableRuleACInsts) <$> M.lookup i (get sNodes sys)- <*> M.lookup j (get sNodes sys)-- -- | Try to evaluate the truth value of this atom in all models of the- -- constraint system 'sys'.- eval ato = case ato of- Action (ltermNodeId' -> i) fa- | ActionG i fa `M.member` get sGoals sys -> Just True- | otherwise ->- case M.lookup i (get sNodes sys) of- Just ru- | any (fa ==) (get rActs ru) -> Just True- | all (not . runMaude . unifiableLNFacts fa) (get rActs ru) -> Just False- _ -> Nothing-- Less (ltermNodeId' -> i) (ltermNodeId' -> j)- | i == j || j `before` i -> Just False- | i `before` j -> Just True- | isLast sys i && isInTrace sys j -> Just False- | isLast sys j && isInTrace sys i &&- nonUnifiableNodes i j -> Just True- | otherwise -> Nothing-- EqE x y- | x == y -> Just True- | not (runMaude (unifiableLNTerms x y)) -> Just False- | otherwise ->- case (,) <$> ltermNodeId x <*> ltermNodeId y of- Just (i, j)- | i `before` j || j `before` i -> Just False- | nonUnifiableNodes i j -> Just False- _ -> Nothing-- Last (ltermNodeId' -> i)- | isLast sys i -> Just True- | any (isInTrace sys) (nodesAfter i) -> Just False- | otherwise ->- case get sLastAtom sys of- Just j | nonUnifiableNodes i j -> Just False- _ -> Nothing------ | CR-rule *S_∀*: insert all newly implied formulas.-insertImpliedFormulas :: Reduction ChangeIndicator-insertImpliedFormulas = do- sys <- gets id- hnd <- getMaudeHandle- applyChangeList $ do- clause <- (S.toList $ get sFormulas sys) ++- (S.toList $ get sLemmas sys)- implied <- impliedFormulas hnd sys clause- if ( implied `S.notMember` get sFormulas sys &&- implied `S.notMember` get sSolvedFormulas sys )- then return (insertFormula implied)- else []---- | @impliedFormulas se imp@ returns the list of guarded formulas that are--- implied by @se@.-impliedFormulas :: MaudeHandle -> System -> LNGuarded -> [LNGuarded]-impliedFormulas hnd sys gf0 =- case openGuarded gf `evalFresh` avoid gf of- Just (All, _vs, antecedent, succedent) -> do- let (actions, otherAtoms) = partitionEithers $ map prepare antecedent- succedent' = gall [] otherAtoms succedent- subst <- candidateSubsts emptySubst actions- return $ unskolemizeLNGuarded $ applySkGuarded subst succedent'- _ -> []- where- gf = skolemizeGuarded gf0-- prepare (Action i fa) = Left (i, fa)- prepare ato = Right (fmap (fmapTerm (fmap Free)) ato)-- sysActions = do (i, fa) <- allActions sys- return (skolemizeTerm (varTerm i), skolemizeFact fa)-- candidateSubsts subst [] = do- return subst- candidateSubsts subst (a:as) = do- sysAct <- sysActions- subst' <- (`runReader` hnd) $ matchAction sysAct (applySkAction subst a)- candidateSubsts (compose subst' subst) as------------------------------------------------------------------------------------ Terms, facts, and formulas with skolem constants----------------------------------------------------------------------------------- | A constant type that supports names and skolem constants. We use the--- skolem constants to represent fixed free variables from the constraint--- system during matching the atoms of a guarded clause to the atoms of the--- constraint system.-data SkConst = SkName Name- | SkConst LVar- deriving( Eq, Ord, Show, Data, Typeable )--type SkTerm = VTerm SkConst LVar-type SkFact = Fact SkTerm-type SkSubst = Subst SkConst LVar-type SkGuarded = LGuarded SkConst---- | A term with skolem constants and bound variables-type BSkTerm = VTerm SkConst BLVar---- | An term with skolem constants and bound variables-type BSkAtom = Atom BSkTerm--instance IsConst SkConst----- Skolemization of terms without bound variables.-----------------------------------------------------skolemizeTerm :: LNTerm -> SkTerm-skolemizeTerm = fmapTerm conv- where- conv :: Lit Name LVar -> Lit SkConst LVar- conv (Var v) = Con (SkConst v)- conv (Con n) = Con (SkName n)--skolemizeFact :: LNFact -> Fact SkTerm-skolemizeFact = fmap skolemizeTerm--skolemizeAtom :: BLAtom -> BSkAtom-skolemizeAtom = fmap skolemizeBTerm--skolemizeGuarded :: LNGuarded -> SkGuarded-skolemizeGuarded = mapGuardedAtoms (const skolemizeAtom)--applySkTerm :: SkSubst -> SkTerm -> SkTerm-applySkTerm subst t = applyVTerm subst t--applySkFact :: SkSubst -> SkFact -> SkFact-applySkFact subst = fmap (applySkTerm subst)--applySkAction :: SkSubst -> (SkTerm,SkFact) -> (SkTerm,SkFact)-applySkAction subst (t,f) = (applySkTerm subst t, applySkFact subst f)----- Skolemization of terms with bound variables.--------------------------------------------------skolemizeBTerm :: VTerm Name BLVar -> BSkTerm-skolemizeBTerm = fmapTerm conv- where- conv :: Lit Name BLVar -> Lit SkConst BLVar- conv (Var (Free x)) = Con (SkConst x)- conv (Var (Bound b)) = Var (Bound b)- conv (Con n) = Con (SkName n)--unskolemizeBTerm :: BSkTerm -> VTerm Name BLVar-unskolemizeBTerm t = fmapTerm conv t- where- conv :: Lit SkConst BLVar -> Lit Name BLVar- conv (Con (SkConst x)) = Var (Free x)- conv (Var (Bound b)) = Var (Bound b)- conv (Var (Free v)) = error $ "unskolemizeBTerm: free variable " ++- show v++" found in "++show t- conv (Con (SkName n)) = Con n--unskolemizeBLAtom :: BSkAtom -> BLAtom-unskolemizeBLAtom = fmap unskolemizeBTerm--unskolemizeLNGuarded :: SkGuarded -> LNGuarded-unskolemizeLNGuarded = mapGuardedAtoms (const unskolemizeBLAtom)--applyBSkTerm :: SkSubst -> VTerm SkConst BLVar -> VTerm SkConst BLVar-applyBSkTerm subst =- go- where- go t = case viewTerm t of- Lit l -> applyBLLit l- FApp o as -> fApp o (map go as)-- applyBLLit :: Lit SkConst BLVar -> VTerm SkConst BLVar- applyBLLit l@(Var (Free v)) =- maybe (lit l) (fmapTerm (fmap Free)) (imageOf subst v)- applyBLLit l = lit l--applyBSkAtom :: SkSubst -> Atom (VTerm SkConst BLVar) -> Atom (VTerm SkConst BLVar)-applyBSkAtom subst = fmap (applyBSkTerm subst)--applySkGuarded :: SkSubst -> LGuarded SkConst -> LGuarded SkConst-applySkGuarded subst = mapGuardedAtoms (const $ applyBSkAtom subst)---- Matching--------------matchAction :: (SkTerm, SkFact) -> (SkTerm, SkFact) -> WithMaude [SkSubst]-matchAction (i1, fa1) (i2, fa2) =- solveMatchLTerm sortOfSkol (i1 `matchWith` i2 <> fa1 `matchFact` fa2)- where- sortOfSkol (SkName n) = sortOfName n- sortOfSkol (SkConst v) = lvarSort v
− src/Theory/Constraint/Solver/Types.hs
@@ -1,150 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ Common types for our constraint solver. They must be declared jointly--- because there is a recursive dependency between goals, proof contexts, and--- case distinctions.-module Theory.Constraint.Solver.Types (-- -- * Proof context- ProofContext(..)- , InductionHint(..)-- , pcSignature- , pcRules- , pcInjectiveFactInsts- , pcCaseDists- , pcCaseDistKind- , pcUseInduction- , pcTraceQuantifier- , pcMaudeHandle-- -- ** Classified rules- , ClassifiedRules(..)- , emptyClassifiedRules- , crConstruct- , crDestruct- , crProtocol- , joinAllRules- , nonSilentRules-- -- * Precomputed case distinctions.- , CaseDistinction(..)-- , cdGoal- , cdCases-- ) where--import Prelude hiding (id, (.))--import Data.Binary-import Data.DeriveTH-import Data.Label hiding (get)-import qualified Data.Label as L-import Data.Monoid (Monoid(..))-import qualified Data.Set as S--import Control.Basics-import Control.Category-import Control.DeepSeq--import Logic.Connectives-import Theory.Constraint.System-import Theory.Model---------------------------------------------------------------------------- ClassifiedRules-------------------------------------------------------------------------data ClassifiedRules = ClassifiedRules- { _crProtocol :: [RuleAC] -- all protocol rules- , _crDestruct :: [RuleAC] -- destruction rules- , _crConstruct :: [RuleAC] -- construction rules- }- deriving( Eq, Ord, Show )--$(mkLabels [''ClassifiedRules])---- | The empty proof rule set.-emptyClassifiedRules :: ClassifiedRules-emptyClassifiedRules = ClassifiedRules [] [] []---- | @joinAllRules rules@ computes the union of all rules classified in--- @rules@.-joinAllRules :: ClassifiedRules -> [RuleAC]-joinAllRules (ClassifiedRules a b c) = a ++ b ++ c---- | Extract all non-silent rules.-nonSilentRules :: ClassifiedRules -> [RuleAC]-nonSilentRules = filter (not . null . L.get rActs) . joinAllRules------------------------------------------------------------------------------------ Proof Context----------------------------------------------------------------------------------- | A big-step case distinction.-data CaseDistinction = CaseDistinction- { _cdGoal :: Goal -- start goal of case distinction- -- disjunction of named sequents with premise being solved; each name- -- being the path of proof steps required to arrive at these cases- , _cdCases :: Disj ([String], System)- }- deriving( Eq, Ord, Show )--data InductionHint = UseInduction | AvoidInduction- deriving( Eq, Ord, Show )---- | A proof context contains the globally fresh facts, classified rewrite--- rules and the corresponding precomputed premise case distinction theorems.-data ProofContext = ProofContext- { _pcSignature :: SignatureWithMaude- , _pcRules :: ClassifiedRules- , _pcInjectiveFactInsts :: S.Set FactTag- , _pcCaseDistKind :: CaseDistKind- , _pcCaseDists :: [CaseDistinction]- , _pcUseInduction :: InductionHint- , _pcTraceQuantifier :: SystemTraceQuantifier- }- deriving( Eq, Ord, Show )--$(mkLabels [''ProofContext, ''CaseDistinction])----- | The 'MaudeHandle' of a proof-context.-pcMaudeHandle :: ProofContext :-> MaudeHandle-pcMaudeHandle = sigmMaudeHandle . pcSignature---- Instances---------------instance HasFrees CaseDistinction where- foldFrees f th =- foldFrees f (L.get cdGoal th) `mappend`- foldFrees f (L.get cdCases th)-- mapFrees f th = CaseDistinction <$> mapFrees f (L.get cdGoal th)- <*> mapFrees f (L.get cdCases th)----- NFData------------$( derive makeBinary ''CaseDistinction)-$( derive makeBinary ''ClassifiedRules)-$( derive makeBinary ''InductionHint)--$( derive makeNFData ''CaseDistinction)-$( derive makeNFData ''ClassifiedRules)-$( derive makeNFData ''InductionHint)
− src/Theory/Constraint/System.hs
@@ -1,482 +0,0 @@-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ This is the public interface for constructing and deconstructing constraint--- systems. The interface for performing constraint solving provided by--- "Theory.Constraint.Solver".-module Theory.Constraint.System (- -- * Constraints- module Theory.Constraint.System.Constraints-- -- * Constraint systems- , System-- -- ** Construction- , emptySystem-- , SystemTraceQuantifier(..)- , formulaToSystem-- -- ** Node constraints- , sNodes- , allKDConcs-- , nodeRule- , nodeConcNode- , nodePremNode- , nodePremFact- , nodeConcFact- , resolveNodePremFact- , resolveNodeConcFact-- -- ** Actions- , allActions- , allKUActions- , unsolvedActionAtoms- -- FIXME: The two functions below should also be prefixed with 'unsolved'- , kuActionAtoms- , standardActionAtoms-- -- ** Edge and chain constraints- , sEdges- , unsolvedChains-- -- ** Temporal ordering- , sLessAtoms-- , rawLessRel- , rawEdgeRel-- , alwaysBefore- , isInTrace-- -- ** The last node- , sLastAtom- , isLast-- -- ** Equations- , module Theory.Tools.EquationStore- , sEqStore- , sSubst- , sConjDisjEqs-- -- ** Formulas- , sFormulas- , sSolvedFormulas-- -- ** Lemmas- , sLemmas- , insertLemmas-- -- ** Keeping track of typing assumptions- , CaseDistKind(..)- , sCaseDistKind-- -- ** Goals- , GoalStatus(..)- , gsSolved- , gsLoopBreaker- , gsNr-- , sGoals- , sNextGoalNr-- -- * Pretty-printing- , prettySystem- , prettyNonGraphSystem-- ) where--import Prelude hiding (id, (.))--import Data.Binary-import qualified Data.DAG.Simple as D-import Data.DeriveTH-import Data.List (foldl', partition)-import qualified Data.Map as M-import Data.Maybe (fromMaybe)-import Data.Monoid (Monoid(..))-import qualified Data.Set as S--import Control.Basics-import Control.Category-import Control.DeepSeq--import Data.Label ((:->), mkLabels)-import qualified Extension.Data.Label as L--import Logic.Connectives-import Theory.Constraint.System.Constraints-import Theory.Model-import Theory.Text.Pretty-import Theory.Tools.EquationStore------------------------------------------------------------------------------------- Types----------------------------------------------------------------------------------- | Whether we are checking for the existence of a trace satisfiying a the--- current constraint system or whether we're checking that no traces--- satisfies the current constraint system.-data SystemTraceQuantifier = ExistsSomeTrace | ExistsNoTrace- deriving( Eq, Ord, Show )---- | Case dinstinction kind that are allowed. The order of the kinds--- corresponds to the subkinding relation: untyped < typed.-data CaseDistKind = UntypedCaseDist | TypedCaseDist- deriving( Eq )--instance Show CaseDistKind where- show UntypedCaseDist = "untyped"- show TypedCaseDist = "typed"--instance Ord CaseDistKind where- compare UntypedCaseDist UntypedCaseDist = EQ- compare UntypedCaseDist TypedCaseDist = LT- compare TypedCaseDist UntypedCaseDist = GT- compare TypedCaseDist TypedCaseDist = EQ---- | The status of a 'Goal'. Use its 'Semigroup' instance to combine the--- status info of goals that collapse.-data GoalStatus = GoalStatus- { _gsSolved :: Bool- -- True if the goal has been solved already.- , _gsNr :: Integer- -- The number of the goal: we use it to track the creation order of- -- goals.- , _gsLoopBreaker :: Bool- -- True if this goal should be solved with care because it may lead to- -- non-termination.- }- deriving( Eq, Ord, Show )---- | A constraint system.-data System = System- { _sNodes :: M.Map NodeId RuleACInst- , _sEdges :: S.Set Edge- , _sLessAtoms :: S.Set (NodeId, NodeId)- , _sLastAtom :: Maybe NodeId- , _sEqStore :: EqStore- , _sFormulas :: S.Set LNGuarded- , _sSolvedFormulas :: S.Set LNGuarded- , _sLemmas :: S.Set LNGuarded- , _sGoals :: M.Map Goal GoalStatus- , _sNextGoalNr :: Integer- , _sCaseDistKind :: CaseDistKind- }- -- NOTE: Don't forget the update 'substSystem' in- -- "Constraint.Solver.Reduction" when adding further fields to the- -- constraint system.- deriving( Eq, Ord )--$(mkLabels [''System, ''GoalStatus])----- Further accessors------------------------- | Label to access the free substitution of the equation store.-sSubst :: System :-> LNSubst-sSubst = eqsSubst . sEqStore---- | Label to access the conjunction of disjunctions of fresh substutitution in--- the equation store.-sConjDisjEqs :: System :-> Conj (SplitId, S.Set (LNSubstVFresh))-sConjDisjEqs = eqsConj . sEqStore------------------------------------------------------------------------------------- Constraint system construction----------------------------------------------------------------------------------- | The empty constraint system, which is logically equivalent to true.-emptySystem :: CaseDistKind -> System-emptySystem = System- M.empty S.empty S.empty Nothing emptyEqStore- S.empty S.empty S.empty- M.empty 0---- | Returns the constraint system that has to be proven to show that given--- formula holds in the context of the given theory.-formulaToSystem :: [LNGuarded] -- ^ Axioms to add- -> CaseDistKind -- ^ Case distinction kind- -> SystemTraceQuantifier -- ^ Trace quantifier- -> LNFormula- -> System-formulaToSystem axioms kind traceQuantifier fm =- insertLemmas safetyAxioms- $ L.set sFormulas (S.singleton gf2)- $ (emptySystem kind)- where- (safetyAxioms, otherAxioms) = partition isSafetyFormula axioms- gf0 = formulaToGuarded_ fm- gf1 = case traceQuantifier of- ExistsSomeTrace -> gf0- ExistsNoTrace -> gnot gf0- -- Non-safety axioms must be added to the formula, as they render the set- -- of traces non-prefix-closed, which makes the use of induction unsound.- gf2 = gconj $ gf1 : otherAxioms---- | Add a lemma / additional assumption to a constraint system.-insertLemma :: LNGuarded -> System -> System-insertLemma =- go- where- go (GConj conj) = foldr (.) id $ map go $ getConj conj- go fm = L.modify sLemmas (S.insert fm)---- | Add lemmas / additional assumptions to a constraint system.-insertLemmas :: [LNGuarded] -> System -> System-insertLemmas fms sys = foldl' (flip insertLemma) sys fms----------------------------------------------------------------------------------- Queries------------------------------------------------------------------------------------ Nodes----------------- | A list of all KD-conclusions in the 'System'.-allKDConcs :: System -> [(NodeId, RuleACInst, LNTerm)]-allKDConcs sys = do- (i, ru) <- M.toList $ L.get sNodes sys- (_, kFactView -> Just (DnK, m)) <- enumConcs ru- return (i, ru, m)---- | @nodeRule v@ accesses the rule label of node @v@ under the assumption that--- it is present in the sequent.-nodeRule :: NodeId -> System -> RuleACInst-nodeRule v se =- fromMaybe errMsg $ M.lookup v $ L.get sNodes se- where- errMsg = error $- "nodeRule: node '" ++ show v ++ "' does not exist in sequent\n" ++- render (nest 2 $ prettySystem se)----- | @nodePremFact prem se@ computes the fact associated to premise @prem@ in--- sequent @se@ under the assumption that premise @prem@ is a a premise in--- @se@.-nodePremFact :: NodePrem -> System -> LNFact-nodePremFact (v, i) se = L.get (rPrem i) $ nodeRule v se---- | @nodePremNode prem@ is the node that this premise is referring to.-nodePremNode :: NodePrem -> NodeId-nodePremNode = fst---- | All facts associated to this node premise.-resolveNodePremFact :: NodePrem -> System -> Maybe LNFact-resolveNodePremFact (v, i) se = lookupPrem i =<< M.lookup v (L.get sNodes se)---- | The fact associated with this node conclusion, if there is one.-resolveNodeConcFact :: NodeConc -> System -> Maybe LNFact-resolveNodeConcFact (v, i) se = lookupConc i =<< M.lookup v (L.get sNodes se)---- | @nodeConcFact (NodeConc (v, i))@ accesses the @i@-th conclusion of the--- rule associated with node @v@ under the assumption that @v@ is labeled with--- a rule that has an @i@-th conclusion.-nodeConcFact :: NodeConc -> System -> LNFact-nodeConcFact (v, i) = L.get (rConc i) . nodeRule v---- | 'nodeConcNode' @c@ compute the node-id of the node conclusion @c@.-nodeConcNode :: NodeConc -> NodeId-nodeConcNode = fst----- Actions--------------- | All actions that hold in a sequent.-unsolvedActionAtoms :: System -> [(NodeId, LNFact)]-unsolvedActionAtoms sys =- do (ActionG i fa, status) <- M.toList (L.get sGoals sys)- guard (not $ L.get gsSolved status)- return (i, fa)---- | All actions that hold in a sequent.-allActions :: System -> [(NodeId, LNFact)]-allActions sys =- unsolvedActionAtoms sys- <|> do (i, ru) <- M.toList $ L.get sNodes sys- (,) i <$> L.get rActs ru---- | All actions that hold in a sequent.-allKUActions :: System -> [(NodeId, LNFact, LNTerm)]-allKUActions sys = do- (i, fa@(kFactView -> Just (UpK, m))) <- allActions sys- return (i, fa, m)---- | The standard actions, i.e., non-KU-actions.-standardActionAtoms :: System -> [(NodeId, LNFact)]-standardActionAtoms = filter (not . isKUFact . snd) . unsolvedActionAtoms---- | All KU-actions.-kuActionAtoms :: System -> [(NodeId, LNFact, LNTerm)]-kuActionAtoms sys = do- (i, fa@(kFactView -> Just (UpK, m))) <- unsolvedActionAtoms sys- return (i, fa, m)---- Destruction chains-------------------------- | All unsolved destruction chains in the constraint system.-unsolvedChains :: System -> [(NodeConc, NodePrem)]-unsolvedChains sys = do- (ChainG from to, status) <- M.toList $ L.get sGoals sys- guard (not $ L.get gsSolved status)- return (from, to)----- The temporal order-------------------------- | @(from,to)@ is in @rawEdgeRel se@ iff we can prove that there is an--- edge-path from @from@ to @to@ in @se@ without appealing to transitivity.-rawEdgeRel :: System -> [(NodeId, NodeId)]-rawEdgeRel sys = map (nodeConcNode *** nodePremNode) $- [(from, to) | Edge from to <- S.toList $ L.get sEdges sys]- ++ unsolvedChains sys---- | @(from,to)@ is in @rawLessRel se@ iff we can prove that there is a path--- (possibly using the 'Less' relation) from @from@ to @to@ in @se@ without--- appealing to transitivity.-rawLessRel :: System -> [(NodeId,NodeId)]-rawLessRel se = S.toList (L.get sLessAtoms se) ++ rawEdgeRel se---- | Returns a predicate that is 'True' iff the first argument happens before--- the second argument in all models of the sequent.-alwaysBefore :: System -> (NodeId -> NodeId -> Bool)-alwaysBefore sys =- check -- lessRel is cached for partial applications- where- lessRel = rawLessRel sys- check i j =- -- speed-up check by first checking less-atoms- ((i, j) `S.member` L.get sLessAtoms sys)- || (j `S.member` D.reachableSet [i] lessRel)---- | 'True' iff the given node id is guaranteed to be instantiated to an--- index in the trace.-isInTrace :: System -> NodeId -> Bool-isInTrace sys i =- i `M.member` L.get sNodes sys- || isLast sys i- || any ((i ==) . fst) (unsolvedActionAtoms sys)---- | 'True' iff the given node id is guaranteed to be instantiated to the last--- index of the trace.-isLast :: System -> NodeId -> Bool-isLast sys i = Just i == L.get sLastAtom sys------------------------------------------------------------------------------------- Pretty printing ------------------------------------------------------------------------------------- | Pretty print a sequent-prettySystem :: HighlightDocument d => System -> d-prettySystem se = vcat $- map combine- [ ("nodes", vcat $ map prettyNode $ M.toList $ L.get sNodes se)- , ("actions", fsepList ppActionAtom $ unsolvedActionAtoms se)- , ("edges", fsepList prettyEdge $ S.toList $ L.get sEdges se)- , ("less", fsepList prettyLess $ S.toList $ L.get sLessAtoms se)- , ("unsolved goals", prettyGoals False se)- ]- ++ [prettyNonGraphSystem se]- where- combine (header, d) = fsep [keyword_ header <> colon, nest 2 d]- ppActionAtom (i, fa) = prettyNAtom (Action (varTerm i) fa)---- | Pretty print the non-graph part of the sequent; i.e. equation store and--- clauses.-prettyNonGraphSystem :: HighlightDocument d => System -> d-prettyNonGraphSystem se = vsep $ map combine- [ ("last", maybe (text "none") prettyNodeId $ L.get sLastAtom se)- , ("formulas", vsep $ map prettyGuarded $ S.toList $ L.get sFormulas se)- , ("equations", prettyEqStore $ L.get sEqStore se)- , ("lemmas", vsep $ map prettyGuarded $ S.toList $ L.get sLemmas se)- , ("allowed cases", text $ show $ L.get sCaseDistKind se)- , ("solved formulas", vsep $ map prettyGuarded $ S.toList $ L.get sSolvedFormulas se)- , ("solved goals", prettyGoals True se)- ]- where- combine (header, d) = fsep [keyword_ header <> colon, nest 2 d]---- | Pretty print solved or unsolved goals.-prettyGoals :: HighlightDocument d => Bool -> System -> d-prettyGoals solved sys = vsep $ do- (goal, status) <- M.toList $ L.get sGoals sys- guard (solved == L.get gsSolved status)- let nr = L.get gsNr status- loopBreaker | L.get gsLoopBreaker status = " (loop breaker)"- | otherwise = ""- return $ prettyGoal goal <-> lineComment_ ("nr: " ++ show nr ++ loopBreaker)----- Additional instances--------------------------deriving instance Show System--instance Apply CaseDistKind where- apply = const id--instance HasFrees CaseDistKind where- foldFrees = const mempty- mapFrees = const pure--instance HasFrees GoalStatus where- foldFrees = const mempty- mapFrees = const pure--instance HasFrees System where- foldFrees fun (System a b c d e f g h i j k) =- foldFrees fun a `mappend`- foldFrees fun b `mappend`- foldFrees fun c `mappend`- foldFrees fun d `mappend`- foldFrees fun e `mappend`- foldFrees fun f `mappend`- foldFrees fun g `mappend`- foldFrees fun h `mappend`- foldFrees fun i `mappend`- foldFrees fun j `mappend`- foldFrees fun k-- mapFrees fun (System a b c d e f g h i j k) =- System <$> mapFrees fun a- <*> mapFrees fun b- <*> mapFrees fun c- <*> mapFrees fun d- <*> mapFrees fun e- <*> mapFrees fun f- <*> mapFrees fun g- <*> mapFrees fun h- <*> mapFrees fun i- <*> mapFrees fun j- <*> mapFrees fun k---$( derive makeBinary ''CaseDistKind)-$( derive makeBinary ''GoalStatus)-$( derive makeBinary ''System)-$( derive makeBinary ''SystemTraceQuantifier)--$( derive makeNFData ''CaseDistKind)-$( derive makeNFData ''GoalStatus)-$( derive makeNFData ''System)-$( derive makeNFData ''SystemTraceQuantifier)
− src/Theory/Constraint/System/Constraints.hs
@@ -1,211 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE TemplateHaskell #-}--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ Types representing constraints.-module Theory.Constraint.System.Constraints (- -- * Guarded formulas- module Theory.Constraint.System.Guarded-- -- * Graph constraints- , NodePrem- , NodeConc- , Edge(..)- , Less-- -- * Goal constraints- , Goal(..)- , isActionGoal- , isStandardActionGoal- , isPremiseGoal- , isChainGoal- , isSplitGoal- , isDisjGoal-- -- ** Pretty-printing- , prettyNode- , prettyNodePrem- , prettyNodeConc- , prettyEdge- , prettyLess- , prettyGoal- ) where--import Data.Binary-import Data.DeriveTH-import Data.Generics-import Extension.Data.Monoid (Monoid(..))--import Control.Basics-import Control.DeepSeq--import Text.PrettyPrint.Class-import Text.Unicode--import Logic.Connectives-import Theory.Constraint.System.Guarded-import Theory.Model-import Theory.Text.Pretty-import Theory.Tools.EquationStore----------------------------------------------------------------------------------- Graph part of a sequent ------------------------------------------------------------------------------------- | A premise of a node.-type NodePrem = (NodeId, PremIdx)---- | A conclusion of a node.-type NodeConc = (NodeId, ConcIdx)---- | A labeled edge in a derivation graph.-data Edge = Edge {- eSrc :: NodeConc- , eTgt :: NodePrem- }- deriving (Show, Ord, Eq, Data, Typeable)---- | A *⋖* constraint between 'NodeId's.-type Less = (NodeId, NodeId)---- Instances---------------instance Apply Edge where- apply subst (Edge from to) = Edge (apply subst from) (apply subst to)--instance HasFrees Edge where- foldFrees f (Edge x y) = foldFrees f x `mappend` foldFrees f y- mapFrees f (Edge x y) = Edge <$> mapFrees f x <*> mapFrees f y------------------------------------------------------------------------------------ Goals----------------------------------------------------------------------------------- | A 'Goal' denotes that a constraint reduction rule is applicable, which--- might result in case splits. We either use a heuristic to decide what goal--- to solve next or leave the choice to user (in case of the interactive UI).-data Goal =- ActionG LVar LNFact- -- ^ An action that must exist in the trace.- | ChainG NodeConc NodePrem- -- A destruction chain.- | PremiseG NodePrem LNFact- -- ^ A premise that must have an incoming direct edge.- | SplitG SplitId- -- ^ A case split over equalities.- | DisjG (Disj LNGuarded)- -- ^ A case split over a disjunction.- deriving( Eq, Ord, Show )---- Indicators----------------isActionGoal :: Goal -> Bool-isActionGoal (ActionG _ _) = True-isActionGoal _ = False--isStandardActionGoal :: Goal -> Bool-isStandardActionGoal (ActionG _ fa) = not (isKUFact fa)-isStandardActionGoal _ = False--isPremiseGoal :: Goal -> Bool-isPremiseGoal (PremiseG _ _) = True-isPremiseGoal _ = False--isChainGoal :: Goal -> Bool-isChainGoal (ChainG _ _) = True-isChainGoal _ = False--isSplitGoal :: Goal -> Bool-isSplitGoal (SplitG _) = True-isSplitGoal _ = False--isDisjGoal :: Goal -> Bool-isDisjGoal (DisjG _) = True-isDisjGoal _ = False------ Instances---------------instance HasFrees Goal where- foldFrees f goal = case goal of- ActionG i fa -> foldFrees f i <> foldFrees f fa- PremiseG p fa -> foldFrees f p <> foldFrees f fa- ChainG c p -> foldFrees f c <> foldFrees f p- SplitG i -> foldFrees f i- DisjG x -> foldFrees f x-- mapFrees f goal = case goal of- ActionG i fa -> ActionG <$> mapFrees f i <*> mapFrees f fa- PremiseG p fa -> PremiseG <$> mapFrees f p <*> mapFrees f fa- ChainG c p -> ChainG <$> mapFrees f c <*> mapFrees f p- SplitG i -> SplitG <$> mapFrees f i- DisjG x -> DisjG <$> mapFrees f x--instance Apply Goal where- apply subst goal = case goal of- ActionG i fa -> ActionG (apply subst i) (apply subst fa)- PremiseG p fa -> PremiseG (apply subst p) (apply subst fa)- ChainG c p -> ChainG (apply subst c) (apply subst p)- SplitG i -> SplitG (apply subst i)- DisjG x -> DisjG (apply subst x)------------------------------------------------------------------------------------ Pretty printing ------------------------------------------------------------------------------------- | Pretty print a node.-prettyNode :: HighlightDocument d => (NodeId, RuleACInst) -> d-prettyNode (v,ru) = prettyNodeId v <> colon <-> prettyRuleACInst ru---- | Pretty print a node conclusion.-prettyNodeConc :: HighlightDocument d => NodeConc -> d-prettyNodeConc (v, ConcIdx i) = parens (prettyNodeId v <> comma <-> int i)---- | Pretty print a node premise.-prettyNodePrem :: HighlightDocument d => NodePrem -> d-prettyNodePrem (v, PremIdx i) = parens (prettyNodeId v <> comma <-> int i)---- | Pretty print a edge as @src >-i--j-> tgt@.-prettyEdge :: HighlightDocument d => Edge -> d-prettyEdge (Edge c p) =- prettyNodeConc c <-> operator_ ">-->" <-> prettyNodePrem p---- | Pretty print a less-atom as @src < tgt@.-prettyLess :: HighlightDocument d => Less -> d-prettyLess (i, j) = prettyNAtom $ Less (varTerm i) (varTerm j)---- | Pretty print a goal.-prettyGoal :: HighlightDocument d => Goal -> d-prettyGoal (ActionG i fa) = prettyNAtom (Action (varTerm i) fa)-prettyGoal (ChainG c p) =- prettyNodeConc c <-> operator_ "~~>" <-> prettyNodePrem p-prettyGoal (PremiseG (i, (PremIdx v)) fa) =- -- Note that we can use "▷" for conclusions once we need them.- prettyLNFact fa <-> text ("▶" ++ subscript (show v)) <-> prettyNodeId i- -- prettyNodePrem p <> brackets (prettyLNFact fa)-prettyGoal (DisjG (Disj [])) = text "Disj" <-> operator_ "(⊥)"-prettyGoal (DisjG (Disj gfs)) = fsep $- punctuate (operator_ " ∥") (map (nest 1 . parens . prettyGuarded) gfs)- -- punctuate (operator_ " |") (map (nest 1 . parens . prettyGuarded) gfs)-prettyGoal (SplitG x) =- text "splitEqs" <> parens (text $ show (unSplitId x))---- Derived instances-----------------------$( derive makeBinary ''Edge)-$( derive makeBinary ''Goal)--$( derive makeNFData ''Edge)-$( derive makeNFData ''Goal)
− src/Theory/Constraint/System/Dot.hs
@@ -1,519 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}--- |--- Copyright : (c) 2010, 2011 Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ Conversion of the graph part of a sequent to a Graphviz Dot file.-module Theory.Constraint.System.Dot (- nonEmptyGraph- , dotSystemLoose- , dotSystemCompact- , compressSystem- , BoringNodeStyle(..)- ) where--import Data.Char (isSpace)-import Data.Color-import qualified Data.DAG.Simple as D-import qualified Data.Foldable as F-import Data.List-import qualified Data.Map as M-import Data.Maybe-import Data.Monoid (Any(..))-import qualified Data.Set as S-import Safe--import Extension.Data.Label-import Extension.Prelude--import Control.Basics-import Control.Monad.Reader-import Control.Monad.State (StateT, evalStateT)--import qualified Text.Dot as D-import Text.PrettyPrint.Class--import Theory.Constraint.System-import Theory.Model-import Theory.Text.Pretty (opAction)---- | 'True' iff the dotted system will be a non-empty graph.-nonEmptyGraph :: System -> Bool-nonEmptyGraph sys = not $- M.null (get sNodes sys) && null (unsolvedActionAtoms sys) &&- null (unsolvedChains sys) &&- S.null (get sEdges sys) && S.null (get sLessAtoms sys)--type NodeColorMap = M.Map (RuleInfo ProtoRuleACInstInfo IntrRuleACInfo) (HSV Double)-type SeDot = ReaderT (System, NodeColorMap) (StateT DotState D.Dot)---- | State to avoid multiple drawing of the same entity.-data DotState = DotState {- _dsNodes :: M.Map NodeId D.NodeId- , _dsPrems :: M.Map NodePrem D.NodeId- , _dsConcs :: M.Map NodeConc D.NodeId- , _dsSingles :: M.Map (NodeConc, NodePrem) D.NodeId- }--$(mkLabels [''DotState])---- | Lift a 'D.Dot' action.-liftDot :: D.Dot a -> SeDot a-liftDot = lift . lift---- | All edges in a bipartite graph that have neither start point nor endpoint--- in common with any other edge.-singleEdges :: (Ord a, Ord b) => [(a,b)] -> [(a,b)]-singleEdges es =- singles fst es `intersect` singles snd es- where- singles proj = concatMap single . groupOn proj . sortOn proj- single [] = error "impossible"- single [x] = return x- single _ = mzero---- | Get a lighter color.-lighter :: HSV Double -> RGB Double-lighter = hsvToRGB -- fmap (\c -> 1 - 0.3*(1-c)) . hsvToRGB---- | Ensure that a 'SeDot' action is only executed once by querying and--- updating the 'DotState' accordingly.-dotOnce :: Ord k- => (DotState :-> M.Map k D.NodeId) -- ^ Accessor to map storing this type of actions.- -> k -- ^ Action index.- -> SeDot D.NodeId -- ^ Action to execute only once.- -> SeDot D.NodeId-dotOnce mapL k dot = do- i <- join $ (maybe dot return . M.lookup k) `liftM` getM mapL- modM mapL (M.insert k i)- return i--dotNode :: NodeId -> SeDot D.NodeId-dotNode v = dotOnce dsNodes v $ do- (se, colorMap) <- ask- let nodes = get sNodes se- dot info moreStyle facts = do- vId <- liftDot $ D.node $ [("label", show v ++ info),("shape","ellipse")]- ++ moreStyle- _ <- facts vId- return vId-- case M.lookup v nodes of- Nothing -> do- dot "" [] (const $ return ()) -- \vId -> do- {-- premIds <- mapM dotPrem- [ NodePremFact v fa- | SeRequires v' fa <- S.toList $ get sRequires se- , v == v' ]- sequence_ [ dotIntraRuleEdge premId vId | premId <- premIds ]- -}- Just ru -> do- let- color = M.lookup (get rInfo ru) colorMap- nodeColor = maybe "white" (rgbToHex . lighter) color- dot (label ru) [("fillcolor", nodeColor),("style","filled")] $ \vId -> do- premIds <- mapM dotPrem- [ (v,i) | (i,_) <- enumPrems ru ]- concIds <- mapM dotConc- [ (v,i) | (i,_) <- enumConcs ru ]- sequence_ [ dotIntraRuleEdge premId vId | premId <- premIds ]- sequence_ [ dotIntraRuleEdge vId concId | concId <- concIds ]- where- label ru = " : " ++ render nameAndActs- where- nameAndActs =- ruleInfo (prettyProtoRuleName . get praciName) prettyIntrRuleACInfo (get rInfo ru) <->- brackets (vcat $ punctuate comma $ map prettyLNFact $ get rActs ru)---- | An edge from a rule node to its premises or conclusions.-dotIntraRuleEdge :: D.NodeId -> D.NodeId -> SeDot ()-dotIntraRuleEdge from to = liftDot $ D.edge from to [("color","gray")]--{---- | An edge from a rule node to some of its premises or conclusions.-dotNonFixedIntraRuleEdge :: D.NodeId -> D.NodeId -> SeDot ()-dotNonFixedIntraRuleEdge from to =- liftDot $ D.edge from to [("color","steelblue")]--}---- | The style of a node displaying a fact.-factNodeStyle :: LNFact -> [(String,String)]-factNodeStyle fa- | isJust (kFactView fa) = []- | otherwise = [("fillcolor","gray85"),("style","filled")]---- | An edge that shares no endpoints with another edge and is therefore--- contracted.------ FIXME: There may be too many edges being contracted.-dotSingleEdge :: (NodeConc, NodePrem) -> SeDot D.NodeId-dotSingleEdge edge@(_, to) = dotOnce dsSingles edge $ do- se <- asks fst- let fa = nodePremFact to se- label = render $ prettyLNFact fa- liftDot $ D.node $ [("label", label),("shape", "hexagon")]- ++ factNodeStyle fa---- | A compressed edge.-dotTrySingleEdge :: Eq c- => ((NodeConc, NodePrem) -> c) -> c- -> SeDot D.NodeId -> SeDot D.NodeId-dotTrySingleEdge sel x dot = do- singles <- getM dsSingles- maybe dot (return . snd) $ find ((x ==) . sel . fst) $ M.toList singles---- | Premises.-dotPrem :: NodePrem -> SeDot D.NodeId-dotPrem prem@(v, i) =- dotOnce dsPrems prem $ dotTrySingleEdge snd prem $ do- nodes <- asks (get sNodes . fst)- let ppPrem = show prem -- FIXME: Use better pretty printing here- (label, moreStyle) = fromMaybe (ppPrem, []) $ do- ru <- M.lookup v nodes- fa <- lookupPrem i ru- return ( render $ prettyLNFact fa- , factNodeStyle fa- )- liftDot $ D.node $ [("label", label),("shape",shape)]- ++ moreStyle- where- shape = "invtrapezium"---- | Conclusions.-dotConc :: NodeConc -> SeDot D.NodeId-dotConc =- dotNodeWithIndex dsConcs fst rConcs (id *** getConcIdx) "trapezium"- where- dotNodeWithIndex stateSel edgeSel ruleSel unwrap shape x0 =- dotOnce stateSel x0 $ dotTrySingleEdge edgeSel x0 $ do- let x = unwrap x0- nodes <- asks (get sNodes . fst)- let (label, moreStyle) = fromMaybe (show x, []) $ do- ru <- M.lookup (fst x) nodes- fa <- (`atMay` snd x) $ get ruleSel ru- return ( render $ prettyLNFact fa- , factNodeStyle fa- )- liftDot $ D.node $ [("label", label),("shape",shape)]- ++ moreStyle------ | Convert the sequent to a 'D.Dot' action representing this sequent as a--- graph in the GraphViz format. The style is loose in the sense that each--- premise and conclusion gets its own node.-dotSystemLoose :: System -> D.Dot ()-dotSystemLoose se =- (`evalStateT` DotState M.empty M.empty M.empty M.empty) $- (`runReaderT` (se, nodeColorMap (M.elems $ get sNodes se))) $ do- liftDot $ setDefaultAttributes- -- draw single edges with matching facts.- mapM_ dotSingleEdge $ singleEdges $ do- Edge from to <- S.toList $ get sEdges se- -- FIXME: ensure that conclusion and premise are equal- guard (nodeConcFact from se == nodePremFact to se)- return (from, to)- sequence_ $ do- (v, ru) <- M.toList $ get sNodes se- (i, _) <- enumConcs ru- return (dotConc (v, i))- sequence_ $ do- (v, ru) <- M.toList $ get sNodes se- (i, _) <- enumPrems ru- return (dotPrem (v,i))- -- FIXME: Also dot unsolved actions.- mapM_ dotNode $ M.keys $ get sNodes se- mapM_ dotEdge $ S.toList $ get sEdges se- mapM_ dotChain $ unsolvedChains se- mapM_ dotLess $ S.toList $ get sLessAtoms se- where- dotEdge (Edge src tgt) = do- mayNid <- M.lookup (src,tgt) `liftM` getM dsSingles- maybe (dotGenEdge [] src tgt) (const $ return ()) mayNid-- dotChain (src, tgt) =- dotGenEdge [("style","dashed"),("color","green")] src tgt-- dotLess (src, tgt) = do- srcId <- dotNode src- tgtId <- dotNode tgt- liftDot $ D.edge srcId tgtId- [("color","black"),("style","dotted")] -- FIXME: Reactivate,("constraint","false")]- -- setting constraint to false ignores less-edges when ranking nodes.-- dotGenEdge style src tgt = do- srcId <- dotConc src- tgtId <- dotPrem tgt- liftDot $ D.edge srcId tgtId style----- | Set default attributes for nodes and edges.-setDefaultAttributes :: D.Dot ()-setDefaultAttributes = do- D.attribute ("nodesep","0.3")- D.attribute ("ranksep","0.3")- D.nodeAttributes [("fontsize","8"),("fontname","Helvetica"),("width","0.3"),("height","0.2")]- D.edgeAttributes [("fontsize","8"),("fontname","Helvetica")]----- | Compute a color map for nodes labelled with a proof rule info of one of--- the given rules.-nodeColorMap :: [RuleACInst] -> NodeColorMap-nodeColorMap rules =- M.fromList $- [ (get rInfo ru, getColor (gIdx, mIdx))- | (gIdx, grp) <- groups, (mIdx, ru) <- zip [0..] grp ]- where- groupIdx ru | isDestrRule ru = 0- | isConstrRule ru = 2- | isFreshRule ru || isISendRule ru = 3- | otherwise = 1-- -- groups of rules labeled with their index in the group- groups = [ (gIdx, [ ru | ru <- rules, gIdx == groupIdx ru])- | gIdx <- [0..3]- ]-- -- color for each member of a group- colors = M.fromList $ lightColorGroups intruderHue (map (length . snd) groups)- getColor idx = fromMaybe (HSV 0 1 1) $ M.lookup idx colors-- -- The hue of the intruder rules- intruderHue :: Double- intruderHue = 18 / 360----------------------------------------------------------------------------------- Record based dotting----------------------------------------------------------------------------------- | The style for nodes of the intruder.-data BoringNodeStyle = FullBoringNodes | CompactBoringNodes- deriving( Eq, Ord, Show )----- | Dot a node in record based (compact) format.-dotNodeCompact :: BoringNodeStyle -> NodeId -> SeDot D.NodeId-dotNodeCompact boringStyle v = dotOnce dsNodes v $ do- (se, colorMap) <- ask- let hasOutgoingEdge =- or [ v == v' | Edge (v', _) _ <- S.toList $ get sEdges se ]- case M.lookup v $ get sNodes se of- Nothing -> case filter ((v ==) . fst) (unsolvedActionAtoms se) of- [] -> mkSimpleNode (show v) []- as -> let lbl = (fsep $ punctuate comma $ map (prettyLNFact . snd) as)- <-> opAction <-> text (show v)- attrs | any (isKUFact . snd) as = [("color","gray")]- | otherwise = [("color","darkblue")]- in mkSimpleNode (render lbl) attrs- Just ru -> do- let color = M.lookup (get rInfo ru) colorMap- nodeColor = maybe "white" (rgbToHex . lighter) color- attrs = [("fillcolor", nodeColor),("style","filled")]- ids <- mkNode ru attrs hasOutgoingEdge- let prems = [ ((v, i), nid) | (Just (Left i), nid) <- ids ]- concs = [ ((v, i), nid) | (Just (Right i), nid) <- ids ]- modM dsPrems $ M.union $ M.fromList prems- modM dsConcs $ M.union $ M.fromList concs- return $ fromJust $ lookup Nothing ids- where-- mkSimpleNode lbl attrs =- liftDot $ D.node $ [("label", lbl),("shape","ellipse")] ++ attrs-- mkNode ru attrs hasOutgoingEdge- -- single node, share node-id for all premises and conclusions- | boringStyle == CompactBoringNodes &&- (isIntruderRule ru || isFreshRule ru) = do- let lbl | hasOutgoingEdge = show v ++ " : " ++ showRuleCaseName ru- | otherwise = concatMap snd as- nid <- mkSimpleNode lbl []- return [ (key, nid) | (key, _) <- ps ++ as ++ cs ]- -- full record syntax- | otherwise =- fmap snd $ liftDot $ (`D.record` attrs) $- D.vcat $ map D.hcat $ map (map (uncurry D.portField)) $- filter (not . null) [ps, as, cs]- where- ps = renderRow [ (Just (Left i), prettyLNFact p) | (i, p) <- enumPrems ru ]- as = renderRow [ (Nothing, ruleLabel ) ]- cs = renderRow [ (Just (Right i), prettyLNFact c) | (i, c) <- enumConcs ru ]-- ruleLabel =- prettyNodeId v <-> colon <-> text (showRuleCaseName ru) <>- (brackets $ vcat $ punctuate comma $ map prettyLNFact $ get rActs ru)-- renderRow annDocs =- zipWith (\(ann, _) lbl -> (ann, lbl)) annDocs $- -- magic factor 1.3 compensates for space gained due to- -- non-propertional font- renderBalanced 100 (max 30 . round . (* 1.3)) (map snd annDocs)-- renderBalanced :: Double -- ^ Total available width- -> (Double -> Int) -- ^ Convert available space to actual line-width.- -> [Doc] -- ^ Initial documents- -> [String] -- ^ Rendered documents- renderBalanced _ _ [] = []- renderBalanced totalWidth conv docs =- zipWith (\w d -> widthRender (conv (ratio * w)) d) usedWidths docs- where- oneLineRender = renderStyle (defaultStyle { mode = OneLineMode })- widthRender w = scaleIndent . renderStyle (defaultStyle { lineLength = w })- usedWidths = map (fromIntegral . length . oneLineRender) docs- ratio = totalWidth / sum usedWidths- scaleIndent line = case span isSpace line of- (spaces, rest) ->- -- spaces are not wide-enough by default => scale them up- let n = (1.5::Double) * fromIntegral (length spaces)- in replicate (round n) ' ' ++ rest------ | Dot a sequent in compact form (one record per rule), if there is anything--- to draw.-dotSystemCompact :: BoringNodeStyle -> System -> D.Dot ()-dotSystemCompact boringStyle se =- (`evalStateT` DotState M.empty M.empty M.empty M.empty) $- (`runReaderT` (se, nodeColorMap (M.elems $ get sNodes se))) $ do- liftDot $ setDefaultAttributes- mapM_ (dotNodeCompact boringStyle) $ M.keys $ get sNodes se- mapM_ (dotNodeCompact boringStyle . fst) $ unsolvedActionAtoms se- F.mapM_ dotEdge $ get sEdges se- F.mapM_ dotChain $ unsolvedChains se- F.mapM_ dotLess $ get sLessAtoms se- where- missingNode shape label = liftDot $ D.node $ [("label", render label),("shape",shape)]- dotPremC prem = dotOnce dsPrems prem $ missingNode "invtrapezium" $ prettyNodePrem prem- dotConcC conc = dotOnce dsConcs conc $ missingNode "trapezium" $ prettyNodeConc conc- dotEdge (Edge src tgt) = do- let check p = maybe False p (resolveNodePremFact tgt se) ||- maybe False p (resolveNodeConcFact src se)- attrs | check isProtoFact =- [("style","bold"),("weight","10.0")] ++- (guard (check isPersistentFact) >> [("color","gray50")])- | check isKFact = [("color","orangered2")]- | otherwise = [("color","gray30")]- dotGenEdge attrs src tgt-- dotGenEdge style src tgt = do- srcId <- dotConcC src- tgtId <- dotPremC tgt- liftDot $ D.edge srcId tgtId style-- dotChain (src, tgt) =- dotGenEdge [("style","dashed"),("color","green")] src tgt-- dotLess (src, tgt) = do- srcId <- dotNodeCompact boringStyle src- tgtId <- dotNodeCompact boringStyle tgt- liftDot $ D.edge srcId tgtId- [("color","black"),("style","dotted")] -- FIXME: reactivate ,("constraint","false")]- -- setting constraint to false ignores less-edges when ranking nodes.------------------------------------------------------------------------------------ Compressed versions of a sequent----------------------------------------------------------------------------------- | Drop 'Less' atoms entailed by the edges of the 'System'.-dropEntailedOrdConstraints :: System -> System-dropEntailedOrdConstraints se =- modify sLessAtoms (S.filter (not . entailed)) se- where- edges = rawEdgeRel se- entailed (from, to) = to `S.member` D.reachableSet [from] edges---- | Unsound compression of the sequent that drops fully connected learns and--- knows nodes.-compressSystem :: System -> System-compressSystem se0 =- foldl' (flip tryHideNodeId) se (frees (get sLessAtoms se, get sNodes se))- where- se = dropEntailedOrdConstraints se0---- | @hideTransferNode v se@ hides node @v@ in sequent @se@ if it is a--- transfer node; i.e., a node annotated with a rule that is one of the--- special intruder rules or a rule with with at most one premise and--- at most one conclusion and both premises and conclusions have incoming--- respectively outgoing edges.------ The compression is chosen such that unly uninteresting nodes are that have--- no open goal are suppressed.-tryHideNodeId :: NodeId -> System -> System-tryHideNodeId v se = fromMaybe se $ do- guard $ (lvarSort v == LSortNode)- && notOccursIn unsolvedChains- && notOccursIn (get sFormulas)- maybe hideAction hideRule (M.lookup v $ get sNodes se)- where- selectPart :: (System :-> S.Set a) -> (a -> Bool) -> [a]- selectPart l p = filter p $ S.toList $ get l se-- notOccursIn :: HasFrees a => (System -> a) -> Bool- notOccursIn proj = not $ getAny $ foldFrees (Any . (v ==)) $ proj se-- -- hide KU-actions deducing pairs, inverses, and simple terms- hideAction = do- guard $ not (null kuActions)- && all eligibleTerm kuActions- && all (\(i, j) -> not (i == j)) lNews- && notOccursIn (standardActionAtoms)- && notOccursIn (get sLastAtom)- && notOccursIn (get sEdges)-- return $ modify sLessAtoms ( (`S.union` S.fromList lNews)- . (`S.difference` S.fromList lIns)- . (`S.difference` S.fromList lOuts)- )- $ modify sGoals (\m -> foldl' removeAction m kuActions)- $ se- where- kuActions = [ x | x@(i,_,_) <- kuActionAtoms se, i == v ]- eligibleTerm (_,_,m) =- isPair m || isInverse m || sortOfLNTerm m == LSortPub-- removeAction m (i, fa, _) = M.delete (ActionG i fa) m-- lIns = selectPart sLessAtoms ((v ==) . snd)- lOuts = selectPart sLessAtoms ((v ==) . fst)- lNews = [ (i, j) | (i, _) <- lIns, (_, j) <- lOuts ]-- -- hide a rule, if it is not "too complicated"- hideRule ru = do- guard $ eligibleRule- && ( length eIns == length (get rPrems ru) )- && ( length eOuts == length (get rConcs ru) )- && ( all (not . selfEdge) eNews )- && notOccursIn (get sLastAtom)- && notOccursIn (get sLessAtoms)- && notOccursIn (unsolvedActionAtoms)-- return $ modify sEdges ( (`S.union` S.fromList eNews)- . (`S.difference` S.fromList eIns)- . (`S.difference` S.fromList eOuts)- )- $ modify sNodes (M.delete v)- $ se- where- eIns = selectPart sEdges ((v ==) . nodePremNode . eTgt)- eOuts = selectPart sEdges ((v ==) . nodeConcNode . eSrc)- eNews = [ Edge cIn pOut | Edge cIn _ <- eIns, Edge _ pOut <- eOuts ]-- selfEdge (Edge cIn pOut) = nodeConcNode cIn == nodePremNode pOut-- eligibleRule =- any ($ ru) [isISendRule, isIRecvRule, isCoerceRule, isFreshRule]- || ( null (get rActs ru) &&- all (\l -> length (get l ru) <= 1) [rPrems, rConcs]- )--{---- | Try to hide a 'NodeId'. This only works if it has only action and either--- edge or less constraints associated.-tryHideNodeId :: NodeId -> System -> System--}-
− src/Theory/Constraint/System/Guarded.hs
@@ -1,650 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeSynonymInstances #-}--- |--- Copyright : (c) 2011 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Benedikt Schmidt <beschmi@gmail.com>--- Portability : GHC only------ Guarded formulas.-module Theory.Constraint.System.Guarded (-- -- * Guarded formulas- Guarded(..)- , LGuarded- , LNGuarded-- -- ** Smart constructors- , gfalse- , gtrue- , gdisj- , gconj- , gex- , gall- , gnot- , ginduct-- , formulaToGuarded- , formulaToGuarded_-- -- ** Transformation- , simplifyGuarded-- , mapGuardedAtoms-- -- ** Queries- , isConjunction- , isDisjunction- , isAllGuarded- , isExGuarded- , isSafetyFormula-- , guardFactTags-- -- ** Conversions to non-bound representations- , bvarToLVar- , openGuarded-- -- ** Substitutions- , substBound- , substBoundAtom- , substFree- , substFreeAtom-- -- ** Pretty-printing- , prettyGuarded-- ) where--import Control.Applicative-import Control.Arrow-import Control.DeepSeq-import Control.Monad.Error-import Control.Monad.Fresh (MonadFresh, scopeFreshness)-import qualified Control.Monad.Trans.PreciseFresh as Precise (Fresh, evalFresh, evalFreshT)--import Debug.Trace--import Data.Binary-import Data.DeriveTH-import Data.Either (partitionEithers)-import Data.Foldable (Foldable(..), foldMap)-import Data.List-import qualified Data.DList as D-import Data.Monoid (Monoid(..))-import Data.Traversable hiding (mapM, sequence)--import Logic.Connectives--import Text.PrettyPrint.Highlight--import Theory.Model------------------------------------------------------------------------------------ Types---------------------------------------------------------------------------------data Guarded s c v = GAto (Atom (VTerm c (BVar v)))- | GDisj (Disj (Guarded s c v))- | GConj (Conj (Guarded s c v))- | GGuarded Quantifier [s] [Atom (VTerm c (BVar v))] (Guarded s c v)- -- ^ Denotes @ALL xs. as => gf@ or @Ex xs. as & gf&- -- depending on the 'Quantifier'.- -- We assume that all bound variables xs occur in- -- f@i atoms in as.- deriving (Eq, Ord, Show)--isConjunction :: Guarded s c v -> Bool-isConjunction (GConj _) = True-isConjunction _ = False--isDisjunction :: Guarded s c v -> Bool-isDisjunction (GDisj _) = True-isDisjunction _ = False--isExGuarded :: Guarded s c v -> Bool-isExGuarded (GGuarded Ex _ _ _) = True-isExGuarded _ = False--isAllGuarded :: Guarded s c v -> Bool-isAllGuarded (GGuarded All _ _ _) = True-isAllGuarded _ = False---- | Check whether the guarded formula is closed and does not contain an--- existential quantifier. This under-approximates the question whether the--- formula is a safety formula. A safety formula @phi@ has the property that a--- trace violating it can never be extended to a trace satisfying it.-isSafetyFormula :: HasFrees (Guarded s c v) => Guarded s c v -> Bool-isSafetyFormula gf0 =- null (frees [gf0]) && noExistential gf0- where- noExistential (GAto _ ) = True- noExistential (GGuarded Ex _ _ _) = False- noExistential (GGuarded All _ _ gf) = noExistential gf- noExistential (GDisj disj) = all noExistential $ getDisj disj- noExistential (GConj conj) = all noExistential $ getConj conj---- | All 'FactTag's that are used in guards.-guardFactTags :: Guarded s c v -> [FactTag]-guardFactTags =- D.toList .- foldGuarded mempty (mconcat . getDisj) (mconcat . getConj) getTags- where- getTags _qua _ss atos inner =- mconcat [ D.singleton tag | Action _ (Fact tag _) <- atos ] <> inner----------------------------------------------------------------------------------- Folding------------------------------------------------------------------------------------ | Fold a guarded formula.-foldGuarded :: (Atom (VTerm c (BVar v)) -> b)- -> (Disj b -> b)- -> (Conj b -> b)- -> (Quantifier -> [s] -> [Atom (VTerm c (BVar v))] -> b -> b)- -> Guarded s c v- -> b-foldGuarded fAto fDisj fConj fGuarded =- go- where- go (GAto a) = fAto a- go (GDisj disj) = fDisj $ fmap go disj- go (GConj conj) = fConj $ fmap go conj- go (GGuarded qua ss as gf) = fGuarded qua ss as (go gf)---- | Fold a guarded formula with scope info.--- The Integer argument denotes the number of--- quantifiers that have been encountered so far.-foldGuardedScope :: (Integer -> Atom (VTerm c (BVar v)) -> b)- -> (Disj b -> b)- -> (Conj b -> b)- -> (Quantifier -> [s] -> Integer -> [Atom (VTerm c (BVar v))] -> b -> b)- -> Guarded s c v- -> b-foldGuardedScope fAto fDisj fConj fGuarded =- go 0- where- go !i (GAto a) = fAto i a- go !i (GDisj disj) = fDisj $ fmap (go i) disj- go !i (GConj conj) = fConj $ fmap (go i) conj- go !i (GGuarded qua ss as gf) =- fGuarded qua ss i' as (go i' gf)- where- i' = i + fromIntegral (length ss)----- | Map a guarded formula with scope info.--- The Integer argument denotes the number of--- quantifiers that have been encountered so far.-mapGuardedAtoms :: (Integer -> Atom (VTerm c (BVar v))- -> Atom (VTerm d (BVar w)))- -> Guarded s c v- -> Guarded s d w-mapGuardedAtoms f =- foldGuardedScope (\i a -> GAto $ f i a) GDisj GConj- (\qua ss i as gf -> GGuarded qua ss (map (f i) as) gf)----------------------------------------------------------------------------------- Instances---------------------------------------------------------------------------------{--instance Functor (Guarded s c) where- fmap f = foldGuarded (GAto . fmap (fmapTerm (fmap (fmap f)))) GDisj GConj- (\qua ss as gf -> GGuarded qua ss (map (fmap (fmapTerm (fmap (fmap f)))) as) gf)--}--instance Foldable (Guarded s c) where- foldMap f = foldGuarded (foldMap (foldMap (foldMap (foldMap f))))- (mconcat . getDisj)- (mconcat . getConj)- (\_qua _ss as b -> foldMap (foldMap (foldMap (foldMap (foldMap f)))) as `mappend` b)--traverseGuarded :: (Applicative f, Ord c, Ord v, Ord a)- => (a -> f v) -> Guarded s c a -> f (Guarded s c v)-traverseGuarded f = foldGuarded (liftA GAto . traverse (traverseTerm (traverse (traverse f))))- (liftA GDisj . sequenceA)- (liftA GConj . sequenceA)- (\qua ss as gf -> GGuarded qua ss <$> traverse (traverse (traverseTerm (traverse (traverse f)))) as <*> gf)--instance Ord c => HasFrees (Guarded (String, LSort) c LVar) where- foldFrees f = foldMap (foldFrees f)- mapFrees f = traverseGuarded (mapFrees f)----- FIXME: remove name hints for variables for saturation?-type LGuarded c = Guarded (String, LSort) c LVar----------------------------------------------------------------------------------- Substitutions of bound for free and vice versa----------------------------------------------------------------------------------- | @substBoundAtom s a@ substitutes each occurence of a bound variables @i@--- in @dom(s)@ with the corresponding free variable @x=s(i)@ in the atom @a@.-substBoundAtom :: Ord c => [(Integer,LVar)] -> Atom (VTerm c (BVar LVar)) -> Atom (VTerm c (BVar LVar))-substBoundAtom s = fmap (fmapTerm (fmap subst))- where subst bv@(Bound i') = case lookup i' s of- Just x -> Free x- Nothing -> bv- subst fv = fv---- | @substBound s gf@ substitutes each occurence of a bound--- variable @i@ in @dom(s)@ with the corresponding free variable--- @s(i)=x@ in all atoms in @gf@.-substBound :: Ord c => [(Integer,LVar)] -> LGuarded c -> LGuarded c-substBound s = mapGuardedAtoms (\j a -> substBoundAtom [(i+j,v) | (i,v) <- s] a)----- | @substFreeAtom s a@ substitutes each occurence of a free variables @v@--- in @dom(s)@ with the bound variables @i=s(v)@ in the atom @a@.-substFreeAtom :: Ord c- => [(LVar,Integer)]- -> Atom (VTerm c (BVar LVar)) -> Atom (VTerm c (BVar LVar))-substFreeAtom s = fmap (fmapTerm (fmap subst))- where subst fv@(Free x) = case lookup x s of- Just i -> Bound i- Nothing -> fv- subst bv = bv---- | @substFreeAtom s gf@ substitutes each occurence of a free variables--- @v in dom(s)@ with the correpsonding bound variables @i=s(v)@--- in all atoms in @gf@.-substFree :: Ord c => [(LVar,Integer)] -> LGuarded c -> LGuarded c-substFree s = mapGuardedAtoms (\j a -> substFreeAtom [(v,i+j) | (v,i) <- s] a)---- | Assuming that there are no more bound variables left in an atom of a--- formula, convert it to an atom with free variables only.-bvarToLVar :: Ord c => Atom (VTerm c (BVar LVar)) -> Atom (VTerm c LVar)-bvarToLVar =- fmap (fmapTerm (fmap (foldBVar boundError id)))- where- boundError v = error $ "bvarToLVar: left-over bound variable '"- ++ show v ++ "'"---- | Provided an 'Atom' does not contain a bound variable, it is converted to--- the type of atoms without bound varaibles.-unbindAtom :: (Ord c, Ord v) => Atom (VTerm c (BVar v)) -> Maybe (Atom (VTerm c v))-unbindAtom = traverse (traverseTerm (traverse (foldBVar (const Nothing) Just)))------------------------------------------------------------------------------------ Opening and Closing----------------------------------------------------------------------------------- | @openGuarded gf@ returns @Just (qua,vs,ats,gf')@ if @gf@ is a guarded--- clause and @Nothing@ otherwise. In the first case, @quao@ is the quantifier,--- @vs@ is a list of fresh variables, @ats@ is the antecedent, and @gf'@ is the--- succedent. In both antecedent and succedent, the bound variables are--- replaced by @vs@.-openGuarded :: (Ord c, MonadFresh m)- => LGuarded c -> m (Maybe (Quantifier, [LVar], [Atom (VTerm c LVar)], LGuarded c))-openGuarded (GGuarded qua vs as gf) = do- xs <- mapM (\(n,s) -> freshLVar n s) vs- return $ Just (qua, xs, openas xs, opengf xs)- where- openas xs = map (bvarToLVar . substBoundAtom (subst xs)) as- opengf xs = substBound (subst xs) gf- subst xs = zip [0..] (reverse xs)-openGuarded _ = return Nothing---- | @closeGuarded vs ats gf@ is a smart constructor for @GGuarded@.-closeGuarded :: Ord c => Quantifier -> [LVar] -> [Atom (VTerm c LVar)]- -> LGuarded c -> LGuarded c-closeGuarded qua vs as gf =- (case qua of Ex -> gex; All -> gall) vs' as' gf'- where- as' = map (substFreeAtom s . fmap (fmapTerm (fmap Free))) as- gf' = substFree s gf- s = zip (reverse vs) [0..]- vs' = map (lvarName &&& lvarSort) vs------------------------------------------------------------------------------------ Conversion and negation---------------------------------------------------------------------------------type LNGuarded = Guarded (String,LSort) Name LVar--instance Apply LNGuarded where- apply subst = mapGuardedAtoms (const $ apply subst)----- | @gtf b@ returns the guarded formula f with @b <-> f@.-gtf :: Bool -> Guarded s c v-gtf False = GDisj (Disj [])-gtf True = GConj (Conj [])---- | @gfalse@ returns the guarded formula f with @False <-> f@.-gfalse :: Guarded s c v-gfalse = gtf False---- | @gtrue@ returns the guarded formula f with @True <-> f@.-gtrue :: Guarded s c v-gtrue = gtf True---- | @gnotAtom a@ returns the guarded formula f with @not a <-> f@.-gnotAtom :: Atom (VTerm c (BVar v)) -> Guarded s c v-gnotAtom a = GGuarded All [] [a] gfalse---- | @gconj gfs@ smart constructor for the conjunction of gfs.-gconj :: (Ord s, Ord c, Ord v) => [Guarded s c v] -> Guarded s c v-gconj gfs0 = case concatMap flatten gfs0 of- [gf] -> gf- gfs | any (gfalse ==) gfs -> gfalse- -- FIXME: See 'sortednub' below.- | otherwise -> GConj $ Conj $ nub gfs- where- flatten (GConj conj) = concatMap flatten $ getConj conj- flatten gf = [gf]---- | @gdisj gfs@ smart constructor for the disjunction of gfs.-gdisj :: (Ord s, Ord c, Ord v) => [Guarded s c v] -> Guarded s c v-gdisj gfs0 = case concatMap flatten gfs0 of- [gf] -> gf- gfs | any (gtrue ==) gfs -> gtrue- -- FIXME: Consider using 'sortednub' here. This yields stronger- -- normalizaton for formulas. However, it also means that we loose- -- invariance under renaming free variables, as the order changes,- -- when they are renamed.- | otherwise -> GDisj $ Disj $ nub gfs- where- flatten (GDisj disj) = concatMap flatten $ getDisj disj- flatten gf = [gf]---- @ A smart constructor for @GGuarded Ex@ that removes empty quantifications--- and conjunctions with 'gfalse'.-gex :: (Ord s, Ord c, Ord v)- => [s] -> [Atom (VTerm c (BVar v))] -> Guarded s c v -> Guarded s c v-gex [] as gf = gconj (map GAto as ++ [gf])-gex _ _ gf | gf == gfalse = gfalse-gex ss as gf = GGuarded Ex ss as gf---- @ A smart constructor for @GGuarded All@ that drops implications to 'gtrue'--- and removes empty premises.-gall :: (Eq s, Eq c, Eq v)- => [s] -> [Atom (VTerm c (BVar v))] -> Guarded s c v -> Guarded s c v-gall _ [] gf = gf-gall _ _ gf | gf == gtrue = gtrue-gall ss atos gf = GGuarded All ss atos gf----- Conversion of formulas to guarded formulas-------------------------------------------------- | Local newtype to avoid orphan instance.-newtype ErrorDoc d = ErrorDoc { unErrorDoc :: d }- deriving( Monoid, NFData, Document, HighlightDocument )--instance Document d => Error (ErrorDoc d) where- noMsg = emptyDoc- strMsg = text----- | @formulaToGuarded fm@ returns a guarded formula @gf@ that is--- equivalent to @fm@ under the assumption that this is possible.--- If not, then 'error' is called.-formulaToGuarded_ :: LNFormula -> LNGuarded-formulaToGuarded_ = either (error . render) id . formulaToGuarded---- | @formulaToGuarded fm@ returns a guarded formula @gf@ that is--- equivalent to @fm@ if possible.-formulaToGuarded :: HighlightDocument d => LNFormula -> Either d LNGuarded-formulaToGuarded fmOrig =- either (Left . ppError . unErrorDoc) Right- $ Precise.evalFreshT (convert False fmOrig) (avoidPrecise fmOrig)- where- ppFormula :: HighlightDocument a => LNFormula -> a- ppFormula = nest 2 . doubleQuotes . prettyLNFormula-- ppError d = d $-$ text "in the formula" $-$ ppFormula fmOrig-- convert True (Ato a) = pure $ gnotAtom a- convert False (Ato a) = pure $ GAto a-- convert polarity (Not f) = convert (not polarity) f-- convert True (Conn And f g) = gdisj <$> mapM (convert True) [f, g]- convert False (Conn And f g) = gconj <$> mapM (convert False) [f, g]-- convert True (Conn Or f g) = gconj <$> mapM (convert True) [f, g]- convert False (Conn Or f g) = gdisj <$> mapM (convert False) [f, g]-- convert True (Conn Imp f g ) =- gconj <$> sequence [convert False f, convert True g]- convert False (Conn Imp f g ) =- gdisj <$> sequence [convert True f, convert False g]-- convert polarity (TF b) = pure $ gtf (polarity /= b)-- convert polarity f0@(Qua qua0 _ _) =- -- The quantifier switch stems from our implicit negation of the formula.- case (qua0, polarity) of- (All, True ) -> convAll Ex- (All, False) -> convAll All- (Ex, True ) -> convEx All- (Ex, False) -> convEx Ex- where- noUnguardedVars [] = return ()- noUnguardedVars unguarded = throwError $ vcat- [ fsep $ text "unguarded variable(s)"- : (punctuate comma $- map (quotes . text . show) unguarded)- ++ map text ["in", "the", "subformula"]- , ppFormula f0- ]-- conjActions (Conn And f1 f2) = conjActions f1 ++ conjActions f2- conjActions (Ato a@(Action _ _)) = [Left $ bvarToLVar a]- conjActions f = [Right f]-- convEx qua = do- (xs,_,f) <- openFormulaPrefix f0- case partitionEithers $ conjActions f of- (as, fs) -> do- -- all existentially quantified variables must be guarded- noUnguardedVars (xs \\ frees as)- -- convert all other formulas- gf <- (if polarity then gdisj else gconj)- <$> mapM (convert polarity) fs- return $ closeGuarded qua xs as gf- where-- convAll qua = do- (xs,_,f) <- openFormulaPrefix f0- case f of- Conn Imp ante suc -> case partitionEithers $ conjActions ante of- (as, fs) -> do- -- all universally quantified variables must be guarded- noUnguardedVars (xs \\ frees as)- -- negate formulas in antecedent and combine with body- gf <- (if polarity then gconj else gdisj)- <$> sequence ( map (convert (not polarity)) fs ++- [convert polarity suc] )-- return $ closeGuarded qua xs as gf-- _ -> throwError $- text "universal quantifier without toplevel implication" $-$- ppFormula f0-- convert polarity (Conn Iff f1 f2) =- gconj <$> mapM (convert polarity) [Conn Imp f1 f2, Conn Imp f2 f1]------------------------------------------------------------------------------------ Induction over the trace----------------------------------------------------------------------------------- | Negate a guarded formula.-gnot :: (Ord s, Ord c, Ord v)- => Guarded s c v -> Guarded s c v-gnot =- go- where- go (GGuarded All ss as gf) = gex ss as $ go gf- go (GGuarded Ex ss as gf) = gall ss as $ go gf- go (GAto ato) = gnotAtom ato- go (GDisj disj) = gconj $ map go (getDisj disj)- go (GConj conj) = gdisj $ map go (getConj conj)----- | Checks if a doubly guarded formula is satisfied by the empty trace;--- returns @'Left' errMsg@ if the formula is not doubly guarded.-satisfiedByEmptyTrace :: Guarded s c v -> Either String Bool-satisfiedByEmptyTrace =- foldGuarded- (\_ato -> throwError "atom outside the scope of a quantifier")- (liftM or . sequence . getDisj)- (liftM and . sequence . getConj)- (\qua _ss _as _gf -> return $ qua == All)- -- the empty trace always satisfies guarded all-quantification- -- and always dissatisfies guarded ex-quantification---- | Tries to convert a doubly guarded formula to an induction hypothesis.--- Returns @'Left' errMsg@ if the formula is not last-free or not doubly--- guarded.-toInductionHypothesis :: Ord c => LGuarded c -> Either String (LGuarded c)-toInductionHypothesis =- go- where- go (GGuarded qua ss as gf)- | any isLastAtom as = throwError "formula not last-free"- | otherwise = do- gf' <- go gf- return $ case qua of- All -> gex ss as (gconj $ (gnotAtom <$> lastAtos) ++ [gf'])- Ex -> gall ss as (gdisj $ (GAto <$> lastAtos) ++ [gf'])- where- lastAtos :: [Atom (VTerm c (BVar LVar))]- lastAtos = do- (j, (_, LSortNode)) <- zip [0..] $ reverse ss- return $ Last (varTerm (Bound j))-- go (GAto (Less i j)) = return $ gdisj [GAto (EqE i j), GAto (Less j i)]- go (GAto (Last _)) = throwError "formula not last-free"- go (GAto ato) = return $ gnotAtom ato- go (GDisj disj) = gconj <$> traverse go (getDisj disj)- go (GConj conj) = gdisj <$> traverse go (getConj conj)---- | Try to prove the formula by applying induction over the trace.--- Returns @'Left' errMsg@ if this is not possible. Returns a tuple of--- formulas: one formalzing the proof obligation of the base-case and one--- formalizing the proof obligation of the step-case.-ginduct :: Ord c => LGuarded c -> Either String (LGuarded c, LGuarded c)-ginduct gf = do- unless (null $ frees gf) (throwError "formula not closed")- unless (containsAction gf) (throwError "formula contains no action atom")- baseCase <- satisfiedByEmptyTrace gf- gfIH <- toInductionHypothesis gf- return (gtf baseCase, gconj [gf, gfIH])- where- containsAction = foldGuarded (const True) (or . getDisj) (or . getConj)- (\_ _ as body -> not (null as) || body)----------------------------------------------------------------------------------- Formula Simplification----------------------------------------------------------------------------------- | Simplify a 'Guarded' formula by replacing atoms with their truth value,--- if it can be determined.-simplifyGuarded :: (LNAtom -> Maybe Bool)- -- ^ Partial assignment for truth value of atoms.- -> LNGuarded- -- ^ Original formula- -> Maybe LNGuarded- -- ^ Simplified formula, provided some simplification was- -- performed.-simplifyGuarded valuation fm0- | fm1 /= fm0 = trace (render ppMsg) (Just fm1)- | otherwise = Nothing- where- ppFm = nest 2 . doubleQuotes . prettyGuarded- ppMsg = nest 2 $ text "simplified formula:" $-$- nest 2 (vcat [ ppFm fm0, text "to", ppFm fm1])-- fm1 = simp fm0-- simp fm@(GAto ato) = maybe fm gtf (valuation =<< unbindAtom ato)- simp (GDisj fms) = gdisj $ map simp $ getDisj fms- simp (GConj fms) = gconj $ map simp $ getConj fms- simp (GGuarded All [] atos gf)- | any ((Just False ==) . snd) annAtos = gtrue- | otherwise =- -- keep all atoms that we cannot evaluate yet.- -- NOTE: Here we are missing the opportunity to change the valuation- -- for evaluating the body 'gf'. We could add all atoms that we have- -- as a premise.- gall [] (fst <$> filter ((Nothing ==) . snd) annAtos) (simp gf)- where- -- cache the possibly expensive evaluation of the valuation- annAtos = (\x -> (x, valuation =<< unbindAtom x)) <$> atos-- -- Note that existentials without quantifiers are already eliminated by- -- 'gex'. Moreover, we dealay simplification inside guarded all- -- quantification and guarded existential quantifiers. Their body will be- -- simplified once the quantifiers are gone.- simp fm@(GGuarded _ _ _ _) = fm------------------------------------------------------------------------------------ Pretty Printing----------------------------------------------------------------------------------- | Pretty print a formula.-prettyGuarded :: HighlightDocument d- => LNGuarded -- ^ Guarded Formula.- -> d -- ^ Pretty printed formula.-prettyGuarded fm =- Precise.evalFresh (pp fm) (avoidPrecise fm)- where- pp :: HighlightDocument d => LNGuarded -> Precise.Fresh d- pp (GAto a) = return $ prettyNAtom $ bvarToLVar a-- pp (GDisj (Disj [])) = return $ operator_ "⊥" -- "F"-- pp (GDisj (Disj xs)) = do- ps <- mapM (\x -> opParens <$> pp x) xs- return $ sep $ punctuate (operator_ " ∨") ps- -- return $ sep $ punctuate (operator_ " |") ps-- pp (GConj (Conj [])) = return $ operator_ "⊤" -- "T"-- pp (GConj (Conj xs)) = do- ps <- mapM (\x -> opParens <$> pp x) xs- return $ sep $ punctuate (operator_ " ∧") ps --- " &") ps-- pp gf0@(GGuarded _ _ _ _) =- -- variable names invented here can be reused otherwise- scopeFreshness $ do- Just (qua, vs, atoms, gf) <- openGuarded gf0- let antecedent = (GAto . fmap (fmapTerm (fmap Free))) <$> atoms- connective = operator_ (case qua of All -> "⇒"; Ex -> "∧")- -- operator_ (case qua of All -> "==>"; Ex -> "&")- quantifier = operator_ (ppQuant qua) <-> ppVars vs <> operator_ "."- dante <- nest 1 <$> pp (GConj (Conj antecedent))- case (qua, vs, gf) of- (Ex, _, GConj (Conj [])) ->- return $ sep $ [ quantifier, dante ]- (All, [], GDisj (Disj [])) | gf == gfalse ->- return $ operator_ "¬" <> dante- _ -> do- dsucc <- nest 1 <$> pp gf- return $ sep [ quantifier, sep [dante, connective, dsucc] ]- where- ppVars = fsep . map (text . show)- ppQuant All = "∀" -- "All "- ppQuant Ex = "∃" -- "Ex "----- Derived instances-----------------------$( derive makeBinary ''Guarded)-$( derive makeNFData ''Guarded)
− src/Theory/Model.hs
@@ -1,25 +0,0 @@--- |--- Copyright : (c) 2011-2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ Security protocol model.-module Theory.Model (- module Term.Unification- , module Theory.Model.Atom- , module Theory.Model.Fact- , module Theory.Model.Formula- , module Theory.Model.Rule- , module Theory.Model.Signature- )- where--import Term.LTerm-import Term.Unification-import Theory.Model.Atom-import Theory.Model.Fact-import Theory.Model.Formula-import Theory.Model.Rule-import Theory.Model.Signature
− src/Theory/Model/Atom.hs
@@ -1,156 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}--- {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}--- {-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TemplateHaskell #-}--- {-# LANGUAGE TupleSections #-}-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE ViewPatterns #-}--- {-# OPTIONS_GHC -fno-warn-orphans #-}--- {-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}- -- spurious warnings for view patterns--- |--- Copyright : (c) 2011, 2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ Formulas that represent security properties.-module Theory.Model.Atom(-- -- * Atoms- Atom(..)- , NAtom- , LNAtom-- , isActionAtom- , isLastAtom- , isLessAtom- , isEqAtom-- -- * LFormula- , BLAtom-- -- * Pretty-Printing- , prettyNAtom- )-where--import Control.Basics-import Control.DeepSeq--import Data.Binary-import Data.DeriveTH-import Data.Foldable (Foldable, foldMap)-import Data.Generics-import Data.Monoid (mappend)-import Data.Traversable--import Term.LTerm-import Term.Unification-import Theory.Model.Fact-import Theory.Text.Pretty------------------------------------------------------------------------------------ Atoms----------------------------------------------------------------------------------- | @Atom@'s are the atoms of trace formulas parametrized over arbitrary--- terms.-data Atom t = Action t (Fact t)- | EqE t t- | Less t t- | Last t- deriving( Eq, Ord, Show, Data, Typeable )---- | @LAtom@ are the atoms we actually use in graph formulas input by the user.-type NAtom v = Atom (VTerm Name v)---- | @LAtom@ are the atoms we actually use in graph formulas input by the user.-type LNAtom = Atom LNTerm---- | Atoms built over 'BLTerm's.-type BLAtom = Atom BLTerm----- Instances---------------instance Functor Atom where- fmap f (Action i fa) = Action (f i) (fmap f fa)- fmap f (EqE l r) = EqE (f l) (f r)- fmap f (Less v u) = Less (f v) (f u)- fmap f (Last i) = Last (f i)--instance Foldable Atom where- foldMap f (Action i fa) =- f i `mappend` (foldMap f fa)- foldMap f (EqE l r) = f l `mappend` f r- foldMap f (Less i j) = f i `mappend` f j- foldMap f (Last i) = f i--instance Traversable Atom where- traverse f (Action i fa) =- Action <$> f i <*> traverse f fa- traverse f (EqE l r) = EqE <$> f l <*> f r- traverse f (Less v u) = Less <$> f v <*> f u- traverse f (Last i) = Last <$> f i--instance HasFrees t => HasFrees (Atom t) where- foldFrees f = foldMap (foldFrees f)- mapFrees f = traverse (mapFrees f)--instance Apply LNAtom where- apply subst (Action i fact) = Action (apply subst i) (apply subst fact)- apply subst (EqE l r) = EqE (apply subst l) (apply subst r)- apply subst (Less i j) = Less (apply subst i) (apply subst j)- apply subst (Last i) = Last (apply subst i)--instance Apply BLAtom where- apply subst (Action i fact) = Action (apply subst i) (apply subst fact)- apply subst (EqE l r) = EqE (apply subst l) (apply subst r)- apply subst (Less i j) = Less (apply subst i) (apply subst j)- apply subst (Last i) = Last (apply subst i)----- Queries--------------- | True iff the atom is an action atom.-isActionAtom :: Atom t -> Bool-isActionAtom ato = case ato of Action _ _ -> True; _ -> False---- | True iff the atom is a last atom.-isLastAtom :: Atom t -> Bool-isLastAtom ato = case ato of Last _ -> True; _ -> False---- | True iff the atom is a temporal ordering atom.-isLessAtom :: Atom t -> Bool-isLessAtom ato = case ato of Less _ _ -> True; _ -> False---- | True iff the atom is an equality atom.-isEqAtom :: Atom t -> Bool-isEqAtom ato = case ato of EqE _ _ -> True; _ -> False------------------------------------------------------------------------------------ Pretty-Printing---------------------------------------------------------------------------------prettyNAtom :: (Show v, HighlightDocument d) => NAtom v -> d-prettyNAtom (Action v fa) =- prettyFact prettyNTerm fa <-> opAction <-> text (show v)-prettyNAtom (EqE l r) =- sep [prettyNTerm l <-> opEqual, prettyNTerm r]- -- sep [prettyNTerm l <-> text "≈", prettyNTerm r]-prettyNAtom (Less u v) = text (show u) <-> opLess <-> text (show v)-prettyNAtom (Last i) = operator_ "last" <> parens (text (show i))----- derived instances-----------------------$( derive makeNFData ''Atom)-$( derive makeBinary ''Atom)
− src/Theory/Model/Fact.hs
@@ -1,353 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2011, 2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ Facts used to formulate and reason about protocol execution.-module Theory.Model.Fact (-- -- * Fact- Fact(..)- , Multiplicity(..)- , FactTag(..)-- , matchFact-- -- ** Queries- , isLinearFact- , isPersistentFact- , isProtoFact-- , factTagName- , showFactTag- , showFactTagArity- , factTagArity- , factTagMultiplicity- , factArity- , factMultiplicity-- , DirTag(..)- , kuFact- , kdFact- , kFactView- , dedFactView-- , isKFact- , isKUFact- , isKDFact-- -- ** Construction- , freshFact- , outFact- , inFact- , kLogFact- , dedLogFact- , protoFact-- -- * NFact- , NFact-- -- * LFact- , LFact- , LNFact- , unifyLNFactEqs- , unifiableLNFacts-- -- * Pretty-Printing-- , prettyFact- , prettyNFact- , prettyLNFact-- ) where--import Control.Basics-import Control.DeepSeq--import Data.Binary-import Data.DeriveTH-import Data.Foldable (Foldable(..))-import Data.Generics-import Data.Maybe (isJust)-import Data.Monoid-import Data.Traversable (Traversable(..))--import Term.Unification--import Text.PrettyPrint.Class------------------------------------------------------------------------------------ Fact---------------------------------------------------------------------------------data Multiplicity = Persistent | Linear- deriving( Eq, Ord, Show, Typeable, Data )---- | Fact tags/symbols-data FactTag = ProtoFact Multiplicity String Int- -- ^ A protocol fact together with its arity and multiplicity.- | FreshFact -- ^ Freshly generated value.- | OutFact -- ^ Sent by the protocol- | InFact -- ^ Officially known by the intruder/network.- | KUFact -- ^ Up-knowledge fact in messsage deduction.- | KDFact -- ^ Down-knowledge fact in message deduction.- | DedFact -- ^ Log-fact denoting that the intruder deduced- -- a message using a construction rule.- deriving( Eq, Ord, Show, Typeable, Data )---- | Facts.-data Fact t = Fact- { factTag :: FactTag- , factTerms :: [t]- }- deriving( Eq, Ord, Show, Typeable, Data )----- Instances---------------instance Functor Fact where- fmap f (Fact tag ts) = Fact tag (fmap f ts)--instance Foldable Fact where- foldMap f (Fact _ ts) = foldMap f ts--instance Traversable Fact where- sequenceA (Fact tag ts) = Fact tag <$> sequenceA ts- traverse f (Fact tag ts) = Fact tag <$> traverse f ts--instance Sized t => Sized (Fact t) where- size (Fact _ args) = size args--instance HasFrees t => HasFrees (Fact t) where- foldFrees f = foldMap (foldFrees f)- mapFrees f = traverse (mapFrees f)--instance Apply t => Apply (Fact t) where- apply subst = fmap (apply subst)----- KU and KD facts----------------------- | A direction tag-data DirTag = UpK | DnK- deriving( Eq, Ord, Show )--kdFact, kuFact :: t -> Fact t-kdFact = Fact KDFact . return-kuFact = Fact KUFact . return---- | View a message-deduction fact.-kFactView :: LNFact -> Maybe (DirTag, LNTerm)-kFactView fa = case fa of- Fact KUFact [m] -> Just (UpK, m)- Fact KUFact _ -> errMalformed "kFactView" fa- Fact KDFact [m] -> Just (DnK, m)- Fact KDFact _ -> errMalformed "kFactView" fa- _ -> Nothing---- | View a deduction logging fact.-dedFactView :: LNFact -> Maybe LNTerm-dedFactView fa = case fa of- Fact DedFact [m] -> Just m- Fact DedFact _ -> errMalformed "dedFactView" fa- _ -> Nothing---- | True if the fact is a message-deduction fact.-isKFact :: LNFact -> Bool-isKFact = isJust . kFactView---- | True if the fact is a KU-fact.-isKUFact :: LNFact -> Bool-isKUFact (Fact KUFact _) = True-isKUFact _ = False---- | True if the fact is a KD-fact.-isKDFact :: LNFact -> Bool-isKDFact (Fact KDFact _) = True-isKDFact _ = False---- | Mark a fact as malformed.-errMalformed :: String -> LNFact -> a-errMalformed caller fa =- error $ caller ++ show ": malformed fact: " ++ show fa---- Constructing facts-------------------------- | A fact denoting a message sent by the protocol to the intruder.-outFact :: t -> Fact t-outFact = Fact OutFact . return---- | A fresh fact denotes a fresh unguessable name.-freshFact :: t -> Fact t-freshFact = Fact FreshFact . return---- | A fact denoting that the intruder sent a message to the protocol.-inFact :: t -> Fact t-inFact = Fact InFact . return---- | A fact logging that the intruder knows a message.-kLogFact :: t -> Fact t-kLogFact = protoFact Linear "K" . return---- | A fact logging that the intruder deduced a message using a construction--- rule. We use this to formulate invariants over normal dependency graphs.-dedLogFact :: t -> Fact t-dedLogFact = Fact DedFact . return---- | A protocol fact denotes a fact generated by a protocol rule.-protoFact :: Multiplicity -> String -> [t] -> Fact t-protoFact multi name ts = Fact (ProtoFact multi name (length ts)) ts----- Queries on facts------------------------ | True iff the fact is a non-special protocol fact.-isProtoFact :: Fact t -> Bool-isProtoFact (Fact (ProtoFact _ _ _) _) = True-isProtoFact _ = False---- | True if the fact is a linear fact.-isLinearFact :: Fact t -> Bool-isLinearFact = (Linear ==) . factMultiplicity---- | True if the fact is a persistent fact.-isPersistentFact :: Fact t -> Bool-isPersistentFact = (Persistent ==) . factMultiplicity---- | The multiplicity of a 'FactTag'.-factTagMultiplicity :: FactTag -> Multiplicity-factTagMultiplicity tag = case tag of- ProtoFact multi _ _ -> multi- KUFact -> Persistent- KDFact -> Persistent- _ -> Linear---- | The arity of a 'FactTag'.-factTagArity :: FactTag -> Int-factTagArity tag = case tag of- ProtoFact _ _ k -> k- KUFact -> 1- KDFact -> 1- DedFact -> 1- FreshFact -> 1- InFact -> 1- OutFact -> 1---- | The arity of a 'Fact'.-factArity :: Fact t -> Int-factArity (Fact tag ts)- | length ts == k = k- | otherwise = error $ "factArity: tag of arity " ++ show k ++- " applied to " ++ show (length ts) ++ " terms"- where- k = factTagArity tag---- | The multiplicity of a 'Fact'.-factMultiplicity :: Fact t -> Multiplicity-factMultiplicity = factTagMultiplicity . factTag------------------------------------------------------------------------------------ NFact----------------------------------------------------------------------------------- | Facts with literals containing names and arbitrary variables.-type NFact v = Fact (NTerm v)------------------------------------------------------------------------------------ LFact----------------------------------------------------------------------------------- | Facts with literals arbitrary constants and logical variables.-type LFact c = Fact (LTerm c)---- | Facts used for proving; i.e. variables fixed to logical variables--- and constant fixed to names.-type LNFact = Fact LNTerm---- | Unify a list of @LFact@ equalities.-unifyLNFactEqs :: [Equal LNFact] -> WithMaude [LNSubstVFresh]-unifyLNFactEqs eqs- | all (evalEqual . fmap factTag) eqs =- unifyLNTerm (map (fmap (fAppList . factTerms)) eqs)- | otherwise = return []---- | 'True' iff the two facts are unifiable.-unifiableLNFacts :: LNFact -> LNFact -> WithMaude Bool-unifiableLNFacts fa1 fa2 = (not . null) <$> unifyLNFactEqs [Equal fa1 fa2]---- | @matchLFact t p@ is a complete set of AC matchers for the term fact @t@--- and the pattern fact @p@.-matchFact :: Fact t -- ^ Term- -> Fact t -- ^ Pattern- -> Match t-matchFact t p =- matchOnlyIf (factTag t == factTag p &&- length (factTerms t) == length (factTerms p))- <> mconcat (zipWith matchWith (factTerms t) (factTerms p))----------------------------------------------------------------------------------- Pretty Printing----------------------------------------------------------------------------------- | The name of a fact tag, e.g., @factTagName KUFact = "KU"@.-factTagName :: FactTag -> String-factTagName tag = case tag of- KUFact -> "KU"- KDFact -> "KD"- DedFact -> "Ded"- InFact -> "In"- OutFact -> "Out"- FreshFact -> "Fr"- (ProtoFact _ n _) -> n---- | Show a fact tag as a 'String'. This is the 'factTagName' prefixed with--- the multiplicity.-showFactTag :: FactTag -> String-showFactTag tag =- (++ factTagName tag) $ case factTagMultiplicity tag of- Linear -> ""- Persistent -> "!"---- | Show a fact tag together with its aritiy.-showFactTagArity :: FactTag -> String-showFactTagArity tag = showFactTag tag ++ "/" ++ show (factTagArity tag)---- | Pretty print a fact.-prettyFact :: Document d => (t -> d) -> Fact t -> d-prettyFact ppTerm (Fact tag ts)- | factTagArity tag /= length ts = ppFact ("MALFORMED-" ++ show tag) ts- | otherwise = ppFact (showFactTag tag) ts- where- ppFact n = nestShort' (n ++ "(") ")" . fsep . punctuate comma . map ppTerm---- | Pretty print a 'NFact'.-prettyNFact :: Document d => LNFact -> d-prettyNFact = prettyFact prettyNTerm---- | Pretty print a 'LFact'.-prettyLNFact :: Document d => LNFact -> d-prettyLNFact fa = prettyFact prettyNTerm fa---- derived instances-----------------------$( derive makeBinary ''Multiplicity)-$( derive makeBinary ''FactTag)-$( derive makeBinary ''Fact)--$( derive makeNFData ''Multiplicity)-$( derive makeNFData ''FactTag)-$( derive makeNFData ''Fact)
− src/Theory/Model/Formula.hs
@@ -1,324 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2010-2012 Simon Meier & Benedikt Schmidt--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ Types and operations for handling sorted first-order logic-module Theory.Model.Formula (-- -- * Formulas- Connective(..)- , Quantifier(..)- , Formula(..)- , LNFormula- , LFormula-- , quantify- , openFormula- , openFormulaPrefix--- , unquantify-- -- ** More convenient constructors- , lfalse- , ltrue- , (.&&.)- , (.||.)- , (.==>.)- , (.<=>.)- , exists- , forall-- -- ** General Transformations- , mapAtoms- , foldFormula-- -- ** Pretty-Printing- , prettyLNFormula-- ) where--import Prelude hiding (negate)--import Data.Binary-import Data.DeriveTH-import Data.Foldable (Foldable, foldMap)-import Data.Generics-import Data.Monoid hiding (All)-import Data.Traversable--import Control.Basics-import Control.DeepSeq-import Control.Monad.Fresh-import qualified Control.Monad.Trans.PreciseFresh as Precise--import Theory.Model.Atom--import Text.PrettyPrint.Highlight--import Term.LTerm-import Term.Substitution----------------------------------------------------------------------------------- Types----------------------------------------------------------------------------------- | Logical connectives.-data Connective = And | Or | Imp | Iff- deriving( Eq, Ord, Show, Enum, Bounded, Data, Typeable )---- | Quantifiers.-data Quantifier = All | Ex- deriving( Eq, Ord, Show, Enum, Bounded, Data, Typeable )----- | First-order formulas in locally nameless representation with hints for the--- names/sorts of quantified variables.-data Formula s c v = Ato (Atom (VTerm c (BVar v)))- | TF !Bool- | Not (Formula s c v)- | Conn !Connective (Formula s c v) (Formula s c v)- | Qua !Quantifier s (Formula s c v)---- Folding--------------- | Fold a formula.-{-# INLINE foldFormula #-}-foldFormula :: (Atom (VTerm c (BVar v)) -> b) -> (Bool -> b)- -> (b -> b) -> (Connective -> b -> b -> b)- -> (Quantifier -> s -> b -> b)- -> Formula s c v- -> b-foldFormula fAto fTF fNot fConn fQua =- go- where- go (Ato a) = fAto a- go (TF b) = fTF b- go (Not p) = fNot (go p)- go (Conn c p q) = fConn c (go p) (go q)- go (Qua qua x p) = fQua qua x (go p)---- | Fold a formula.-{-# INLINE foldFormulaScope #-}-foldFormulaScope :: (Integer -> Atom (VTerm c (BVar v)) -> b) -> (Bool -> b)- -> (b -> b) -> (Connective -> b -> b -> b)- -> (Quantifier -> s -> b -> b)- -> Formula s c v- -> b-foldFormulaScope fAto fTF fNot fConn fQua =- go 0- where- go !i (Ato a) = fAto i a- go _ (TF b) = fTF b- go !i (Not p) = fNot (go i p)- go !i (Conn c p q) = fConn c (go i p) (go i q)- go !i (Qua qua x p) = fQua qua x (go (succ i) p)----- Instances---------------{--instance Functor (Formula s c) where- fmap f = foldFormula (Ato . fmap (fmap (fmap (fmap f)))) TF Not Conn Qua--}--instance Foldable (Formula s c) where- foldMap f = foldFormula (foldMap (foldMap (foldMap (foldMap f)))) mempty id- (const mappend) (const $ const id)--traverseFormula :: (Ord v, Ord c, Ord v', Applicative f)- => (v -> f v') -> Formula s c v -> f (Formula s c v')-traverseFormula f = foldFormula (liftA Ato . traverse (traverseTerm (traverse (traverse f))))- (pure . TF) (liftA Not)- (liftA2 . Conn) ((liftA .) . Qua)-{--instance Traversable (Formula a s) where- traverse f = foldFormula (liftA Ato . traverseAtom (traverseTerm (traverseLit (traverseBVar f))))- (pure . TF) (liftA Not)- (liftA2 . Conn) ((liftA .) . Qua)--}---- Abbreviations-------------------infixl 3 .&&.-infixl 2 .||.-infixr 1 .==>.-infix 1 .<=>.---- | Logically true.-ltrue :: Formula a s v-ltrue = TF True---- | Logically false.-lfalse :: Formula a s v-lfalse = TF False--(.&&.), (.||.), (.==>.), (.<=>.) :: Formula a s v -> Formula a s v -> Formula a s v-(.&&.) = Conn And-(.||.) = Conn Or-(.==>.) = Conn Imp-(.<=>.) = Conn Iff----------------------------------------------------------------------------------- Dealing with bound variables----------------------------------------------------------------------------------- | @LFormula@ are FOL formulas with sorts abused to denote both a hint for--- the name of the bound variable, as well as the variable's actual sort.-type LFormula c = Formula (String, LSort) c LVar--type LNFormula = Formula (String, LSort) Name LVar---- | Change the representation of atoms.-mapAtoms :: (Integer -> Atom (VTerm c (BVar v))- -> Atom (VTerm c1 (BVar v1)))- -> Formula s c v -> Formula s c1 v1-mapAtoms f = foldFormulaScope (\i a -> Ato $ f i a) TF Not Conn Qua---- | @openFormula f@ returns @Just (v,Q,f')@ if @f = Q v. f'@ modulo--- alpha renaming and @Nothing otherwise@. @v@ is always chosen to be fresh.-openFormula :: (MonadFresh m, Ord c)- => LFormula c -> Maybe (Quantifier, m (LVar, LFormula c))-openFormula (Qua qua (n,s) fm) =- Just ( qua- , do x <- freshLVar n s- return $ (x, mapAtoms (\i a -> fmap (mapLits (subst x i)) a) fm)- )- where- subst x i (Var (Bound i')) | i == i' = Var $ Free x- subst _ _ l = l--openFormula _ = Nothing--mapLits :: (Ord a, Ord b) => (a -> b) -> Term a -> Term b-mapLits f t = case viewTerm t of- Lit l -> lit . f $ l- FApp o as -> fApp o (map (mapLits f) as)---- | @openFormulaPrefix f@ returns @Just (vs,Q,f')@ if @f = Q v_1 .. v_k. f'@--- modulo alpha renaming and @Nothing otherwise@. @vs@ is always chosen to be--- fresh.-openFormulaPrefix :: (MonadFresh m, Ord c)- => LFormula c -> m ([LVar], Quantifier, LFormula c)-openFormulaPrefix f0 = case openFormula f0 of- Nothing -> error $ "openFormulaPrefix: no outermost quantifier"- Just (q, open) -> do- (x, f) <- open- go q [x] f- where- go q xs f = case openFormula f of- Just (q', open') | q' == q -> do (x', f') <- open'- go q (x' : xs) f'- -- no further quantifier of the same kind => return result- _ -> return (reverse xs, q, f)----- Instances---------------deriving instance Eq LNFormula-deriving instance Show LNFormula-deriving instance Ord LNFormula--instance HasFrees LNFormula where- foldFrees f = foldMap (foldFrees f)- mapFrees f = traverseFormula (mapFrees f)--instance Apply LNFormula where- apply subst = mapAtoms (const $ apply subst)----------------------------------------------------------------------------------- Formulas modulo E and modulo AC----------------------------------------------------------------------------------- | Introduce a bound variable for a free variable.-quantify :: (Ord c, Ord v, Eq v) => v -> Formula s c v -> Formula s c v-quantify x =- mapAtoms (\i a -> fmap (mapLits (fmap (>>= subst i))) a)- where- subst i v | v == x = Bound i- | otherwise = Free v---- | Create a universal quantification with a sort hint for the bound variable.-forall :: (Ord c, Ord v, Eq v) => s -> v -> Formula s c v -> Formula s c v-forall hint x = Qua All hint . quantify x---- | Create a existential quantification with a sort hint for the bound variable.-exists :: (Ord c, Ord v, Eq v) => s -> v -> Formula s c v -> Formula s c v-exists hint x = Qua Ex hint . quantify x----------------------------------------------------------------------------------- Pretty printing----------------------------------------------------------------------------------- | Pretty print a formula.-prettyLFormula :: (HighlightDocument d, MonadFresh m, Ord c)- => (Atom (VTerm c LVar) -> d) -- ^ Function for pretty printing atoms- -> LFormula c -- ^ Formula to pretty print.- -> m d -- ^ Pretty printed formula.-prettyLFormula ppAtom =- pp- where- extractFree (Free v) = v- extractFree (Bound i) = error $ "prettyFormula: illegal bound variable '" ++ show i ++ "'"-- pp (Ato a) = return $ ppAtom (fmap (mapLits (fmap extractFree)) a)- pp (TF True) = return $ operator_ "⊤" -- "T"- pp (TF False) = return $ operator_ "⊥" -- "F"-- pp (Not p) = do- p' <- pp p- return $ operator_ "¬" <> opParens p' -- text "¬" <> parens (pp a)- -- return $ operator_ "not" <> opParens p' -- text "¬" <> parens (pp a)-- pp (Conn op p q) = do- p' <- pp p- q' <- pp q- return $ sep [opParens p' <-> operator_ (ppOp op), opParens q']- where- ppOp And = "∧" -- "&"- ppOp Or = "∨" -- "|"- ppOp Imp = "⇒" -- "==>"- ppOp Iff = "⇔" -- "<=>"-- pp fm@(Qua _ _ _) =- scopeFreshness $ do- (vs,qua,fm') <- openFormulaPrefix fm- d' <- pp fm'- return $ sep- [ operator_ (ppQuant qua) <> ppVars vs <> operator_ "."- , nest 1 d']- where- ppVars = fsep . map (text . show)-- ppQuant All = "∀ " -- "All "- ppQuant Ex = "∃ " -- "Ex "----- | Pretty print a logical formula-prettyLNFormula :: HighlightDocument d => LNFormula -> d-prettyLNFormula fm =- Precise.evalFresh (prettyLFormula prettyNAtom fm) (avoidPrecise fm)----- Derived instances-----------------------$( derive makeBinary ''Connective)-$( derive makeBinary ''Quantifier)-$( derive makeBinary ''Formula)--$( derive makeNFData ''Connective)-$( derive makeNFData ''Quantifier)-$( derive makeNFData ''Formula)
− src/Theory/Model/Rule.hs
@@ -1,632 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeSynonymInstances #-}--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : portable------ Rewriting rules representing protocol execution and intruder deduction. Once--- modulo the full Diffie-Hellman equational theory and once modulo AC.-module Theory.Model.Rule (- -- * General Rules- Rule(..)- , PremIdx(..)- , ConcIdx(..)-- -- ** Accessors- , rInfo- , rPrems- , rConcs- , rActs- , rPrem- , rConc- , lookupPrem- , lookupConc- , enumPrems- , enumConcs-- -- ** Genereal protocol and intruder rules- , RuleInfo(..)- , ruleInfo-- -- * Protocol Rule Information- , ProtoRuleName(..)- , ProtoRuleACInfo(..)- , pracName- , pracVariants- , pracLoopBreakers- , ProtoRuleACInstInfo(..)- , praciName- , praciLoopBreakers- , RuleACConstrs-- -- * Intruder Rule Information- , IntrRuleACInfo(..)-- -- * Concrete Rules- , ProtoRuleE- , ProtoRuleAC- , IntrRuleAC- , RuleAC- , RuleACInst-- -- ** Queries- , HasRuleName(..)- , isIntruderRule- , isDestrRule- , isConstrRule- , isFreshRule- , isIRecvRule- , isISendRule- , isCoerceRule- , nfRule- , isTrivialProtoVariantAC-- -- ** Conversion- , ruleACToIntrRuleAC- , ruleACIntrToRuleAC-- -- ** Construction- , someRuleACInst-- -- ** Unification- , unifyRuleACInstEqs- , unifiableRuleACInsts-- -- * Pretty-Printing- , showRuleCaseName- , prettyProtoRuleName- , prettyRuleName- , prettyProtoRuleE- , prettyProtoRuleAC- , prettyIntrRuleAC- , prettyIntrRuleACInfo- , prettyRuleAC- , prettyLoopBreakers- , prettyRuleACInst-- ) where--import Prelude hiding (id, (.))--import Data.Binary-import qualified Data.ByteString.Char8 as BC-import Data.DeriveTH-import Data.Foldable (foldMap)-import Data.Generics-import Data.List-import Data.Monoid-import Safe--import Control.Basics-import Control.Category-import Control.DeepSeq-import Control.Monad.Bind-import Control.Monad.Reader--import Extension.Data.Label hiding (get)-import qualified Extension.Data.Label as L-import Logic.Connectives--import Term.LTerm-import Term.Rewriting.Norm (nf')-import Term.Unification-import Theory.Model.Fact-import Theory.Text.Pretty----------------------------------------------------------------------------------- General Rule----------------------------------------------------------------------------------- | Rewriting rules with arbitrary additional information and facts with names--- and logical variables.-data Rule i = Rule {- _rInfo :: i- , _rPrems :: [LNFact]- , _rConcs :: [LNFact]- , _rActs :: [LNFact]- }- deriving( Eq, Ord, Show, Data, Typeable )--$(mkLabels [''Rule])---- | An index of a premise. The first premise has index '0'.-newtype PremIdx = PremIdx { getPremIdx :: Int }- deriving( Eq, Ord, Show, Enum, Data, Typeable, Binary, NFData )---- | An index of a conclusion. The first conclusion has index '0'.-newtype ConcIdx = ConcIdx { getConcIdx :: Int }- deriving( Eq, Ord, Show, Enum, Data, Typeable, Binary, NFData )---- | @lookupPrem i ru@ returns the @i@-th premise of rule @ru@, if possible.-lookupPrem :: PremIdx -> Rule i -> Maybe LNFact-lookupPrem i = (`atMay` getPremIdx i) . L.get rPrems---- | @lookupConc i ru@ returns the @i@-th conclusion of rule @ru@, if possible.-lookupConc :: ConcIdx -> Rule i -> Maybe LNFact-lookupConc i = (`atMay` getConcIdx i) . L.get rConcs---- | @rPrem i@ is a lens for the @i@-th premise of a rule.-rPrem :: PremIdx -> (Rule i :-> LNFact)-rPrem i = nthL (getPremIdx i) . rPrems---- | @rConc i@ is a lens for the @i@-th conclusion of a rule.-rConc :: ConcIdx -> (Rule i :-> LNFact)-rConc i = nthL (getConcIdx i) . rConcs---- | Enumerate all premises of a rule.-enumPrems :: Rule i -> [(PremIdx, LNFact)]-enumPrems = zip [(PremIdx 0)..] . L.get rPrems---- | Enumerate all conclusions of a rule.-enumConcs :: Rule i -> [(ConcIdx, LNFact)]-enumConcs = zip [(ConcIdx 0)..] . L.get rConcs---- Instances---------------instance Functor Rule where- fmap f (Rule i ps cs as) = Rule (f i) ps cs as--instance HasFrees i => HasFrees (Rule i) where- foldFrees f (Rule i ps cs as) =- (foldFrees f i `mappend`) $- (foldFrees f ps `mappend`) $- (foldFrees f cs `mappend`) $- (foldFrees f as)-- mapFrees f (Rule i ps cs as) =- Rule <$> mapFrees f i- <*> mapFrees f ps <*> mapFrees f cs <*> mapFrees f as--instance Apply i => Apply (Rule i) where- apply subst (Rule i ps cs as) =- Rule (apply subst i) (apply subst ps) (apply subst cs) (apply subst as)--instance Sized (Rule i) where- size (Rule _ ps cs as) = size ps + size cs + size as----------------------------------------------------------------------------------- Rule information split into intruder rule and protocol rules----------------------------------------------------------------------------------- | Rule information for protocol and intruder rules.-data RuleInfo p i =- ProtoInfo p- | IntrInfo i- deriving( Eq, Ord, Show )---- | @ruleInfo proto intr@ maps the protocol information with @proto@ and the--- intruder information with @intr@.-ruleInfo :: (p -> c) -> (i -> c) -> RuleInfo p i -> c-ruleInfo proto _ (ProtoInfo x) = proto x-ruleInfo _ intr (IntrInfo x) = intr x----- Instances---------------instance (HasFrees p, HasFrees i) => HasFrees (RuleInfo p i) where- foldFrees f = ruleInfo (foldFrees f) (foldFrees f)-- mapFrees f = ruleInfo (fmap ProtoInfo . mapFrees f)- (fmap IntrInfo . mapFrees f)--instance (Apply p, Apply i) => Apply (RuleInfo p i) where- apply subst = ruleInfo (ProtoInfo . apply subst) (IntrInfo . apply subst)------------------------------------------------------------------------------------ Protocol Rule Information----------------------------------------------------------------------------------- | A name of a protocol rule is either one of the special reserved rules or--- some standard rule.-data ProtoRuleName =- FreshRule- | StandRule String -- ^ Some standard protocol rule- deriving( Eq, Ord, Show, Data, Typeable )----- | Information for protocol rules modulo AC. The variants list the possible--- instantiations of the free variables of the rule. The typing is interpreted--- modulo AC; i.e., its variants were also built.-data ProtoRuleACInfo = ProtoRuleACInfo- { _pracName :: ProtoRuleName- , _pracVariants :: Disj (LNSubstVFresh)- , _pracLoopBreakers :: [PremIdx]- }- deriving( Eq, Ord, Show )---- | Information for instances of protocol rules modulo AC.-data ProtoRuleACInstInfo = ProtoRuleACInstInfo- { _praciName :: ProtoRuleName- , _praciLoopBreakers :: [PremIdx]- }- deriving( Eq, Ord, Show )---$(mkLabels [''ProtoRuleACInfo, ''ProtoRuleACInstInfo])----- Instances---------------instance Apply ProtoRuleName where- apply _ = id--instance HasFrees ProtoRuleName where- foldFrees _ = const mempty- mapFrees _ = pure--instance Apply PremIdx where- apply _ = id--instance HasFrees PremIdx where- foldFrees _ = const mempty- mapFrees _ = pure--instance Apply ConcIdx where- apply _ = id--instance HasFrees ConcIdx where- foldFrees _ = const mempty- mapFrees _ = pure--instance HasFrees ProtoRuleACInfo where- foldFrees f (ProtoRuleACInfo na vari breakers) =- foldFrees f na `mappend` foldFrees f vari- `mappend` foldFrees f breakers-- mapFrees f (ProtoRuleACInfo na vari breakers) =- ProtoRuleACInfo na <$> mapFrees f vari <*> mapFrees f breakers--instance Apply ProtoRuleACInstInfo where- apply _ = id--instance HasFrees ProtoRuleACInstInfo where- foldFrees f (ProtoRuleACInstInfo na breakers) =- foldFrees f na `mappend` foldFrees f breakers-- mapFrees f (ProtoRuleACInstInfo na breakers) =- ProtoRuleACInstInfo na <$> mapFrees f breakers------------------------------------------------------------------------------------ Intruder Rule Information----------------------------------------------------------------------------------- | An intruder rule modulo AC is described by its name.-data IntrRuleACInfo =- ConstrRule BC.ByteString- | DestrRule BC.ByteString- | CoerceRule- | IRecvRule- | ISendRule- | PubConstrRule- | FreshConstrRule- deriving( Ord, Eq, Show, Data, Typeable )---- | An intruder rule modulo AC.-type IntrRuleAC = Rule IntrRuleACInfo---- | Converts between these two types of rules, if possible.-ruleACToIntrRuleAC :: RuleAC -> Maybe IntrRuleAC-ruleACToIntrRuleAC (Rule (IntrInfo i) ps cs as) = Just (Rule i ps cs as)-ruleACToIntrRuleAC _ = Nothing---- | Converts between these two types of rules.-ruleACIntrToRuleAC :: IntrRuleAC -> RuleAC-ruleACIntrToRuleAC (Rule ri ps cs as) = Rule (IntrInfo ri) ps cs as---- Instances---------------instance Apply IntrRuleACInfo where- apply _ = id--instance HasFrees IntrRuleACInfo where- foldFrees _ = const mempty- mapFrees _ = pure------------------------------------------------------------------------------------ Concrete rules----------------------------------------------------------------------------------- | A rule modulo E is always a protocol rule. Intruder rules are specified--- abstractly by their operations generating them and are only available once--- their variants are built.-type ProtoRuleE = Rule ProtoRuleName---- | A protocol rule modulo AC.-type ProtoRuleAC = Rule ProtoRuleACInfo---- | A rule modulo AC is either a protocol rule or an intruder rule-type RuleAC = Rule (RuleInfo ProtoRuleACInfo IntrRuleACInfo)---- | A rule instance module AC is either a protocol rule or an intruder rule.--- The info identifies the corresponding rule modulo AC that the instance was--- derived from.-type RuleACInst = Rule (RuleInfo ProtoRuleACInstInfo IntrRuleACInfo)---- Accessing the rule name------------------------------- | Types that have an associated name.-class HasRuleName t where- ruleName :: t -> RuleInfo ProtoRuleName IntrRuleACInfo--instance HasRuleName ProtoRuleE where- ruleName = ProtoInfo . L.get rInfo--instance HasRuleName RuleAC where- ruleName = ruleInfo (ProtoInfo . L.get pracName) IntrInfo . L.get rInfo--instance HasRuleName ProtoRuleAC where- ruleName = ProtoInfo . L.get (pracName . rInfo)--instance HasRuleName IntrRuleAC where- ruleName = IntrInfo . L.get rInfo--instance HasRuleName RuleACInst where- ruleName = ruleInfo (ProtoInfo . L.get praciName) IntrInfo . L.get rInfo----- Queries--------------- | True iff the rule is a destruction rule.-isDestrRule :: HasRuleName r => r -> Bool-isDestrRule ru = case ruleName ru of- IntrInfo (DestrRule _) -> True- _ -> False---- | True iff the rule is a construction rule.-isConstrRule :: HasRuleName r => r -> Bool-isConstrRule ru = case ruleName ru of- IntrInfo (ConstrRule _) -> True- IntrInfo FreshConstrRule -> True- IntrInfo PubConstrRule -> True- IntrInfo CoerceRule -> True- _ -> False---- | True iff the rule is the special fresh rule.-isFreshRule :: HasRuleName r => r -> Bool-isFreshRule = (ProtoInfo FreshRule ==) . ruleName---- | True iff the rule is the special learn rule.-isIRecvRule :: HasRuleName r => r -> Bool-isIRecvRule = (IntrInfo IRecvRule ==) . ruleName---- | True iff the rule is the special knows rule.-isISendRule :: HasRuleName r => r -> Bool-isISendRule = (IntrInfo ISendRule ==) . ruleName---- | True iff the rule is the special coerce rule.-isCoerceRule :: HasRuleName r => r -> Bool-isCoerceRule = (IntrInfo CoerceRule ==) . ruleName---- | True if the messages in premises and conclusions are in normal form-nfRule :: Rule i -> WithMaude Bool-nfRule (Rule _ ps cs as) = reader $ \hnd ->- all (nfFactList hnd) [ps, cs, as]- where- nfFactList hnd xs =- getAll $ foldMap (foldMap (All . (\t -> nf' t `runReader` hnd))) xs---- | True iff the rule is an intruder rule-isIntruderRule :: HasRuleName r => r -> Bool-isIntruderRule ru =- case ruleName ru of IntrInfo _ -> True; ProtoInfo _ -> False---- | True if the protocol rule has only the trivial variant.-isTrivialProtoVariantAC :: ProtoRuleAC -> ProtoRuleE -> Bool-isTrivialProtoVariantAC (Rule info ps as cs) (Rule _ ps' as' cs') =- L.get pracVariants info == Disj [emptySubstVFresh]- && ps == ps' && as == as' && cs == cs'----- Construction------------------type RuleACConstrs = Disj LNSubstVFresh---- | Compute /some/ rule instance of a rule modulo AC. If the rule is a--- protocol rule, then the given typing and variants also need to be handled.-someRuleACInst :: MonadFresh m- => RuleAC- -> m (RuleACInst, Maybe RuleACConstrs)-someRuleACInst =- fmap extractInsts . rename- where- extractInsts (Rule (ProtoInfo i) ps cs as) =- ( Rule (ProtoInfo i') ps cs as- , Just (L.get pracVariants i)- )- where- i' = ProtoRuleACInstInfo (L.get pracName i) (L.get pracLoopBreakers i)- extractInsts (Rule (IntrInfo i) ps cs as) =- ( Rule (IntrInfo i) ps cs as, Nothing )----- Unification------------------- | Unify a list of @RuleACInst@ equalities.-unifyRuleACInstEqs :: [Equal RuleACInst] -> WithMaude [LNSubstVFresh]-unifyRuleACInstEqs eqs- | all unifiable eqs = unifyLNFactEqs $ concatMap ruleEqs eqs- | otherwise = return []- where- unifiable (Equal ru1 ru2) =- L.get rInfo ru1 == L.get rInfo ru2- && length (L.get rPrems ru1) == length (L.get rPrems ru2)- && length (L.get rConcs ru1) == length (L.get rConcs ru2)-- ruleEqs (Equal ru1 ru2) =- zipWith Equal (L.get rPrems ru1) (L.get rPrems ru2) ++- zipWith Equal (L.get rConcs ru1) (L.get rConcs ru2)---- | Are these two rule instances unifiable.-unifiableRuleACInsts :: RuleACInst -> RuleACInst -> WithMaude Bool-unifiableRuleACInsts ru1 ru2 =- (not . null) <$> unifyRuleACInstEqs [Equal ru1 ru2]------------------------------------------------------------------------------------ Fact analysis----------------------------------------------------------------------------------- | Globally unique facts.------ A rule instance removes a fact fa if fa is in the rule's premise but not--- in the rule's conclusion.------ A fact symbol fa is globally fresh with respect to a dependency graph if--- there are no two rule instances that remove the same fact built from fa.------ We are looking for sufficient criterion to prove that a fact symbol is--- globally fresh.------ The Fr symbol is globally fresh by construction.------ We have to track every creation of a globally fresh fact to a Fr fact.------ (And show that the equality of of the created fact implies the equality of--- the corresponding fresh facts. Ignore this for now by assuming that no--- duplication happens.)------ (fa(x1), fr(y1)), (fa(x2), fr(y2)) : x2 = x1 ==> y1 == y2------ And ensure that every duplication is non-unifiable.------ A Fr fact is described------ We track which symbols are not globally fresh.------ All persistent facts are not globally fresh.------ Adding a rule ru.--- All fact symbols that occur twice in the conclusion------ For simplicity: globally fresh fact symbols occur at most once in premise--- and conclusion of a rule.------ A fact is removed by a rule if it occurs in the rules premise--- 1. but doesn't occur in the rule's conclusion--- 2. or does occur but non-unifiable.------ We want a sufficient criterion to prove that a fact is globally unique.----------------------------------------------------------------------------------------- Pretty-Printing----------------------------------------------------------------------------------- | Prefix the name if it is equal to a reserved name.-prefixIfReserved :: String -> String-prefixIfReserved n- | n `elem` reserved = "_" ++ n- | "_" `isPrefixOf` n = "_" ++ n- | otherwise = n- where- reserved = ["Fresh", "irecv", "isend", "coerce", "fresh", "pub"]--prettyProtoRuleName :: Document d => ProtoRuleName -> d-prettyProtoRuleName rn = text $ case rn of- FreshRule -> "Fresh"- StandRule n -> prefixIfReserved n--prettyRuleName :: (HighlightDocument d, HasRuleName (Rule i)) => Rule i -> d-prettyRuleName = ruleInfo prettyProtoRuleName prettyIntrRuleACInfo . ruleName---- | Pretty print the rule name such that it can be used as a case name-showRuleCaseName :: HasRuleName (Rule i) => Rule i -> String-showRuleCaseName =- render . ruleInfo prettyProtoRuleName prettyIntrRuleACInfo . ruleName--prettyIntrRuleACInfo :: Document d => IntrRuleACInfo -> d-prettyIntrRuleACInfo rn = text $ case rn of- IRecvRule -> "irecv"- ISendRule -> "isend"- CoerceRule -> "coerce"- FreshConstrRule -> "fresh"- PubConstrRule -> "pub"- ConstrRule name -> prefixIfReserved ('c' : BC.unpack name)- DestrRule name -> prefixIfReserved ('d' : BC.unpack name)--prettyNamedRule :: (HighlightDocument d, HasRuleName (Rule i))- => d -- ^ Prefix.- -> (i -> d) -- ^ Rule info pretty printing.- -> Rule i -> d-prettyNamedRule prefix ppInfo ru =- prefix <-> prettyRuleName ru <> colon $-$- nest 2 (sep [ nest 1 $ ppFactsList rPrems- , if null (L.get rActs ru)- then operator_ "-->"- else fsep [operator_ "--[", ppFacts rActs, operator_ "]->"]- , nest 1 $ ppFactsList rConcs]) $-$- nest 2 (ppInfo $ L.get rInfo ru)- where- ppList pp = fsep . punctuate comma . map pp- ppFacts proj = ppList prettyLNFact $ L.get proj ru- ppFactsList proj = fsep [operator_ "[", ppFacts proj, operator_ "]"]--prettyProtoRuleACInfo :: HighlightDocument d => ProtoRuleACInfo -> d-prettyProtoRuleACInfo i =- (ppVariants $ L.get pracVariants i) $-$- prettyLoopBreakers i- where- ppVariants (Disj [subst]) | subst == emptySubstVFresh = emptyDoc- ppVariants substs = kwVariantsModulo "AC" $-$ prettyDisjLNSubstsVFresh substs--prettyLoopBreakers :: HighlightDocument d => ProtoRuleACInfo -> d-prettyLoopBreakers i = case breakers of- [] -> emptyDoc- [_] -> lineComment_ $ "loop breaker: " ++ show breakers- _ -> lineComment_ $ "loop breakers: " ++ show breakers- where- breakers = getPremIdx <$> L.get pracLoopBreakers i--prettyProtoRuleE :: HighlightDocument d => ProtoRuleE -> d-prettyProtoRuleE = prettyNamedRule (kwRuleModulo "E") (const emptyDoc)--prettyRuleAC :: HighlightDocument d => RuleAC -> d-prettyRuleAC =- prettyNamedRule (kwRuleModulo "AC")- (ruleInfo prettyProtoRuleACInfo (const emptyDoc))--prettyIntrRuleAC :: HighlightDocument d => IntrRuleAC -> d-prettyIntrRuleAC = prettyNamedRule (kwRuleModulo "AC") (const emptyDoc)--prettyProtoRuleAC :: HighlightDocument d => ProtoRuleAC -> d-prettyProtoRuleAC = prettyNamedRule (kwRuleModulo "AC") prettyProtoRuleACInfo--prettyRuleACInst :: HighlightDocument d => RuleACInst -> d-prettyRuleACInst = prettyNamedRule (kwInstanceModulo "AC") (const emptyDoc)---- derived instances-----------------------$( derive makeBinary ''Rule)-$( derive makeBinary ''ProtoRuleName)-$( derive makeBinary ''ProtoRuleACInfo)-$( derive makeBinary ''ProtoRuleACInstInfo)-$( derive makeBinary ''RuleInfo)-$( derive makeBinary ''IntrRuleACInfo)--$( derive makeNFData ''Rule)-$( derive makeNFData ''ProtoRuleName)-$( derive makeNFData ''ProtoRuleACInfo)-$( derive makeNFData ''ProtoRuleACInstInfo)-$( derive makeNFData ''RuleInfo)-$( derive makeNFData ''IntrRuleACInfo)
− src/Theory/Model/Signature.hs
@@ -1,172 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE TypeSynonymInstances #-}--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : portable------ Signatures for the terms and multiset rewriting rules used to model and--- reason about a security protocol.--- modulo the full Diffie-Hellman equational theory and once modulo AC.-module Theory.Model.Signature (-- -- * Signature type- Signature(..)-- -- ** Pure signatures- , SignaturePure- , emptySignaturePure- , sigpMaudeSig-- -- ** Using Maude to handle operations relative to a 'Signature'- , SignatureWithMaude- , toSignatureWithMaude- , toSignaturePure- , sigmMaudeHandle-- -- ** Pretty-printing- , prettySignaturePure- , prettySignatureWithMaude-- ) where--import Data.Binary-import qualified Data.Label as L--import Control.Applicative-import Control.DeepSeq--import System.IO.Unsafe (unsafePerformIO)--import Term.Maude.Process (MaudeHandle, mhFilePath, mhMaudeSig, startMaude)-import Term.Maude.Signature (MaudeSig, minimalMaudeSig, prettyMaudeSig)-import Theory.Text.Pretty----- | A theory signature.-data Signature a = Signature- { -- The signature of the message algebra- _sigMaudeInfo :: a- }--$(L.mkLabels [''Signature])------------------------------------------------------------------------------------ Pure Signatures----------------------------------------------------------------------------------- | A 'Signature' without an associated Maude process.-type SignaturePure = Signature MaudeSig---- | Access the maude signature.-sigpMaudeSig:: SignaturePure L.:-> MaudeSig-sigpMaudeSig = sigMaudeInfo---- | The empty pure signature.-emptySignaturePure :: SignaturePure-emptySignaturePure = Signature minimalMaudeSig---- Instances---------------deriving instance Eq SignaturePure-deriving instance Ord SignaturePure-deriving instance Show SignaturePure--instance Binary SignaturePure where- put sig = put (L.get sigMaudeInfo sig)- get = Signature <$> get--instance NFData SignaturePure where- rnf (Signature y) = rnf y----------------------------------------------------------------------------------- Signatures with an attached Maude process----------------------------------------------------------------------------------- | A 'Signature' with an associated, running Maude process.-type SignatureWithMaude = Signature MaudeHandle---- | Access the maude handle in a signature.-sigmMaudeHandle :: SignatureWithMaude L.:-> MaudeHandle-sigmMaudeHandle = sigMaudeInfo---- | Ensure that maude is running and configured with the current signature.-toSignatureWithMaude :: FilePath -- ^ Path to Maude executable.- -> SignaturePure- -> IO (SignatureWithMaude)-toSignatureWithMaude maudePath sig = do- hnd <- startMaude maudePath (L.get sigMaudeInfo sig)- return $ sig { _sigMaudeInfo = hnd }----- | The pure signature of a 'SignatureWithMaude'.-toSignaturePure :: SignatureWithMaude -> SignaturePure-toSignaturePure sig = sig { _sigMaudeInfo = mhMaudeSig $ L.get sigMaudeInfo sig }--{- TODO: There should be a finalizer in place such that as soon as the- MaudeHandle is garbage collected, the appropriate command is sent to Maude-- The code below is a crutch and leads to unnecessary complication.----- | Stop the maude process. This operation is unsafe, as there still might be--- thunks that rely on the MaudeHandle to refer to a running Maude process.-unsafeStopMaude :: SignatureWithMaude -> IO (SignaturePure)-unsafeStopMaude = error "unsafeStopMaude: implement"---- | Run an IO action with maude running and configured with a specific--- signature. As there must not be any part of the return value that depends--- on unevaluated calls to the Maude process provided to the inner IO action.-unsafeWithMaude :: FilePath -- ^ Path to Maude executable- -> SignaturePure -- ^ Signature to use- -> (SignatureWithMaude -> IO a) -> IO a-unsafeWithMaude maudePath sig =- bracket (startMaude maudePath sig) unsafeStopMaude---}---- Instances---------------instance Eq SignatureWithMaude where- x == y = toSignaturePure x == toSignaturePure y--instance Ord SignatureWithMaude where- compare x y = compare (toSignaturePure x) (toSignaturePure y)--instance Show SignatureWithMaude where- show = show . toSignaturePure--instance Binary SignatureWithMaude where- put sig@(Signature maude) = do- put (mhFilePath maude)- put (toSignaturePure sig)- -- FIXME: reload the right signature- get = unsafePerformIO <$> (toSignatureWithMaude <$> get <*> get)--instance NFData SignatureWithMaude where- rnf (Signature _maude) = ()----------------------------------------------------------------------------------- Pretty-printing----------------------------------------------------------------------------------- | Pretty-print a signature with maude.-prettySignaturePure :: HighlightDocument d => SignaturePure -> d-prettySignaturePure sig =- prettyMaudeSig $ L.get sigpMaudeSig sig---- | Pretty-print a pure signature.-prettySignatureWithMaude :: HighlightDocument d => SignatureWithMaude -> d-prettySignatureWithMaude sig =- prettyMaudeSig $ mhMaudeSig $ L.get sigmMaudeHandle sig-
− src/Theory/Proof.hs
@@ -1,654 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TupleSections #-}--- |--- Copyright : (c) 2010-2012 Simon Meier & Benedikt Schmidt--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ Types to represent proofs.-module Theory.Proof (- -- * Utilities- LTree(..)- , mergeMapsWith-- -- * Types- , ProofStep(..)- , Proof-- -- ** Paths inside proofs- , ProofPath- , atPath- , insertPaths-- -- ** Folding/modifying proofs- , mapProofInfo- , foldProof- , annotateProof- , ProofStatus(..)- , proofStepStatus-- -- ** Unfinished proofs- , sorry- , unproven-- -- ** Incremental proof construction- , IncrementalProof- , Prover- , runProver- , mapProverProof-- , orelse- , tryProver- , sorryProver- , oneStepProver- , focus- , checkAndExtendProver- , replaceSorryProver- , contradictionProver-- -- ** Explicit representation of a fully automatic prover- , SolutionExtractor(..)- , AutoProver(..)- , runAutoProver-- -- ** Pretty Printing- , prettyProof- , prettyProofWith-- , showProofStatus-- -- ** Parallel Strategy for exploring a proof- , parLTreeDFS-- -- ** Small-step interface to the constraint solver- , module Theory.Constraint.Solver--) where--import Data.Binary-import Data.DeriveTH-import Data.Foldable (Foldable, foldMap)-import Data.List-import qualified Data.Map as M-import Data.Maybe-import Data.Monoid-import Data.Traversable--import Debug.Trace--import Control.Basics-import Control.DeepSeq-import qualified Control.Monad.State as S-import Control.Parallel.Strategies--import Theory.Constraint.Solver-import Theory.Model-import Theory.Text.Pretty------------------------------------------------------------------------------------ Utility: Trees with uniquely labelled edges.----------------------------------------------------------------------------------- | Trees with uniquely labelled edges.-data LTree l a = LNode- { root :: a- , children :: M.Map l (LTree l a)- }- deriving( Eq, Ord, Show )--instance Functor (LTree l) where- fmap f (LNode r cs) = LNode (f r) (M.map (fmap f) cs)--instance Foldable (LTree l) where- foldMap f (LNode x cs) = f x `mappend` foldMap (foldMap f) cs--instance Traversable (LTree l) where- traverse f (LNode x cs) = LNode <$> f x <*> traverse (traverse f) cs---- | A parallel evaluation strategy well-suited for DFS traversal: As soon as--- a node is forced it sparks off the computation of the number of case-maps--- of all its children. This way most of the data is already evaulated, when--- the actual DFS traversal visits it.------ NOT used for now. It sometimes required too much memory.-parLTreeDFS :: Strategy (LTree l a)-parLTreeDFS (LNode x0 cs0) = do- cs0' <- (`parTraversable` cs0) $ \(LNode x cs) -> LNode x <$> rseq cs- return $ LNode x0 (M.map (runEval . parLTreeDFS) cs0')----------------------------------------------------------------------------------- Utility: Merging maps----------------------------------------------------------------------------------- | /O(n+m)/. A generalized union operator for maps with differing types.-mergeMapsWith :: Ord k- => (a -> c) -> (b -> c) -> (a -> b -> c)- -> M.Map k a -> M.Map k b -> M.Map k c-mergeMapsWith leftOnly rightOnly combine l r =- M.map extract $ M.unionWith combine' l' r'- where- l' = M.map (Left . Left) l- r' = M.map (Left . Right) r-- combine' (Left (Left a)) (Left (Right b)) = Right $ combine a b- combine' _ _ = error "mergeMapsWith: impossible"-- extract (Left (Left a)) = leftOnly a- extract (Left (Right b)) = rightOnly b- extract (Right c) = c------------------------------------------------------------------------------------ Proof Steps----------------------------------------------------------------------------------- | A proof steps is a proof method together with additional context-dependent--- information.-data ProofStep a = ProofStep- { psMethod :: ProofMethod- , psInfo :: a- }- deriving( Eq, Ord, Show )--instance Functor ProofStep where- fmap f (ProofStep m i) = ProofStep m (f i)--instance Foldable ProofStep where- foldMap f = f . psInfo--instance Traversable ProofStep where- traverse f (ProofStep m i) = ProofStep m <$> f i--instance HasFrees a => HasFrees (ProofStep a) where- foldFrees f (ProofStep m i) = foldFrees f m `mappend` foldFrees f i- mapFrees f (ProofStep m i) = ProofStep <$> mapFrees f m <*> mapFrees f i----------------------------------------------------------------------------------- Proof Trees----------------------------------------------------------------------------------- | A path to a subproof.-type ProofPath = [CaseName]---- | A proof is a tree of proof steps whose edges are labelled with case names.-type Proof a = LTree CaseName (ProofStep a)---- Unfinished proofs------------------------- | A proof using the 'sorry' proof method.-sorry :: Maybe String -> a -> Proof a-sorry reason ann = LNode (ProofStep (Sorry reason) ann) M.empty---- | A proof denoting an unproven part of the proof.-unproven :: a -> Proof a-unproven = sorry Nothing----- Paths in proofs----------------------- | @prf `atPath` path@ returns the subproof at the @path@ in @prf@.-atPath :: Proof a -> ProofPath -> Maybe (Proof a)-atPath = foldM (flip M.lookup . children)---- | @modifyAtPath f path prf@ applies @f@ to the subproof at @path@,--- if there is one.-modifyAtPath :: (Proof a -> Maybe (Proof a)) -> ProofPath- -> Proof a -> Maybe (Proof a)-modifyAtPath f =- go- where- go [] prf = f prf- go (l:ls) prf = do- let cs = children prf- prf' <- go ls =<< M.lookup l cs- return (prf { children = M.insert l prf' cs })---- | @insertPaths prf@ inserts the path to every proof node.-insertPaths :: Proof a -> Proof (a, ProofPath)-insertPaths =- insertPath []- where- insertPath path (LNode ps cs) =- LNode (fmap (,reverse path) ps)- (M.mapWithKey (\n prf -> insertPath (n:path) prf) cs)----- Utilities for dealing with proofs------------------------------------------ | Apply a function to the information of every proof step.-mapProofInfo :: (a -> b) -> Proof a -> Proof b-mapProofInfo = fmap . fmap---- | @boundProofDepth bound prf@ bounds the depth of the proof @prf@ using--- 'Sorry' steps to replace the cut sub-proofs.-boundProofDepth :: Int -> Proof a -> Proof a-boundProofDepth bound =- go bound- where- go n (LNode ps@(ProofStep _ info) cs)- | 0 < n = LNode ps $ M.map (go (pred n)) cs- | otherwise = sorry (Just $ "bound " ++ show bound ++ " hit") info---- | Fold a proof.-foldProof :: Monoid m => (ProofStep a -> m) -> Proof a -> m-foldProof f =- go- where- go (LNode step cs) = f step `mappend` foldMap go (M.elems cs)---- | Annotate a proof in a bottom-up fashion.-annotateProof :: (ProofStep a -> [b] -> b) -> Proof a -> Proof b-annotateProof f =- go- where- go (LNode step@(ProofStep method _) cs) =- LNode (ProofStep method info') cs'- where- cs' = M.map go cs- info' = f step (map (psInfo . root . snd) (M.toList cs'))---- Proof cutting--------------------- | The status of a 'Proof'.-data ProofStatus =- UndeterminedProof -- ^ All steps are unannotated- | CompleteProof -- ^ The proof is complete: no annotated sorry,- -- no annotated solved step- | IncompleteProof -- ^ There is a annotated sorry,- -- but no annotatd solved step.- | TraceFound -- ^ There is an annotated solved step--instance Monoid ProofStatus where- mempty = CompleteProof-- mappend TraceFound _ = TraceFound- mappend _ TraceFound = TraceFound- mappend IncompleteProof _ = IncompleteProof- mappend _ IncompleteProof = IncompleteProof- mappend _ CompleteProof = CompleteProof- mappend CompleteProof _ = CompleteProof- mappend UndeterminedProof UndeterminedProof = UndeterminedProof---- | The status of a 'ProofStep'.-proofStepStatus :: ProofStep (Maybe a) -> ProofStatus-proofStepStatus (ProofStep _ Nothing ) = UndeterminedProof-proofStepStatus (ProofStep Solved (Just _)) = TraceFound-proofStepStatus (ProofStep (Sorry _) (Just _)) = IncompleteProof-proofStepStatus (ProofStep _ (Just _)) = CompleteProof---{- TODO: Test and probably improve---- | @proveSystem rules se@ tries to construct a proof that @se@ is valid.--- This proof may contain 'Sorry' steps, if the prover is stuck. It can also be--- of infinite depth, if the proof strategy loops.-proveSystemIterDeep :: ProofContext -> System -> Proof System-proveSystemIterDeep rules se0 =- fromJust $ asum $ map (prove se0 . round) $ iterate (*1.5) (3::Double)- where- prove :: System -> Int -> Maybe (Proof System)- prove se bound- | bound < 0 = Nothing- | otherwise =- case next of- [] -> pure $ sorry "prover stuck => possible attack found" se- xs -> asum $ map mkProof xs- where- next = do m <- possibleProofMethods se- (m,) <$> maybe mzero return (execProofMethod rules m se)- mkProof (method, cases) =- LNode (ProofStep method se) <$> traverse (`prove` (bound - 1)) cases--}---- | @checkProof rules se prf@ replays the proof @prf@ against the start--- sequent @se@. A failure to apply a proof method is denoted by a resulting--- proof step without an annotated sequent. An unhandled case is denoted using--- the 'Sorry' proof method.-checkProof :: ProofContext- -> (Int -> System -> Proof (Maybe System)) -- prover for new cases in depth- -> Int -- ^ Original depth- -> System- -> Proof a- -> Proof (Maybe a, Maybe System)-checkProof ctxt prover d sys prf@(LNode (ProofStep method info) cs) =- case (method, execProofMethod ctxt method sys) of- (Sorry reason, _ ) -> sorryNode reason cs- (_ , Just cases) -> node method $ checkChildren cases- (_ , Nothing ) ->- sorryNode (Just "invalid proof step encountered")- (M.singleton "" prf)- where- node m = LNode (ProofStep m (Just info, Just sys))- sorryNode reason cases = node (Sorry reason) (M.map noSystemPrf cases)- noSystemPrf = mapProofInfo (\i -> (Just i, Nothing))-- checkChildren cases = mergeMapsWith- unhandledCase noSystemPrf (checkProof ctxt prover (d + 1)) cases cs- where- unhandledCase = mapProofInfo ((,) Nothing) . prover d---- | Annotate a proof with the constraint systems of all intermediate steps--- under the assumption that all proof steps are valid. If some proof steps--- might be invalid, then you must use 'checkProof', which handles them--- gracefully.-annotateWithSystems :: ProofContext -> System -> Proof () -> Proof System-annotateWithSystems ctxt =- go- where- -- Here we are careful to construct the result such that an inspection of- -- the proof does not force the recomputed constraint systems.- go sysOrig (LNode (ProofStep method _) csOrig) =- LNode (ProofStep method sysOrig) $ M.fromList $ do- (name, prf) <- M.toList csOrig- let sysAnn = extract ("case '" ++ name ++ "' non-existent") $- M.lookup name csAnn- return (name, go sysAnn prf)- where- extract msg = fromMaybe (error $ "annotateWithSystems: " ++ msg)- csAnn = extract "proof method execution failed" $- execProofMethod ctxt method sysOrig------------------------------------------------------------------------------------ Provers: the interface to the outside world.----------------------------------------------------------------------------------- | Incremental proofs are used to represent intermediate results of proof--- checking/construction.-type IncrementalProof = Proof (Maybe System)---- | Provers whose sequencing is handled via the 'Monoid' instance.------ > p1 `mappend` p2------ Is a prover that first runs p1 and then p2 on the resulting proof.-newtype Prover = Prover- { runProver- :: ProofContext -- proof rules to use- -> Int -- proof depth- -> System -- original sequent to start with- -> IncrementalProof -- original proof- -> Maybe IncrementalProof -- resulting proof- }--instance Monoid Prover where- mempty = Prover $ \_ _ _ -> Just- p1 `mappend` p2 = Prover $ \ctxt d se ->- runProver p1 ctxt d se >=> runProver p2 ctxt d se---- | Map the proof generated by the prover.-mapProverProof :: (IncrementalProof -> IncrementalProof) -> Prover -> Prover-mapProverProof f p = Prover $ \ ctxt d se prf -> f <$> runProver p ctxt d se prf---- | Prover that always fails.-failProver :: Prover-failProver = Prover (\ _ _ _ _ -> Nothing)---- | Resorts to the second prover, if the first one is not successful.-orelse :: Prover -> Prover -> Prover-orelse p1 p2 = Prover $ \ctxt d se prf ->- runProver p1 ctxt d se prf `mplus` runProver p2 ctxt d se prf---- | Try to apply a prover. If it fails, just return the original proof.-tryProver :: Prover -> Prover-tryProver = (`orelse` mempty)---- | Try to execute one proof step using the given proof method.-oneStepProver :: ProofMethod -> Prover-oneStepProver method = Prover $ \ctxt _ se _ -> do- cases <- execProofMethod ctxt method se- return $ LNode (ProofStep method (Just se)) (M.map (unproven . Just) cases)---- | Replace the current proof with a sorry step and the given reason.-sorryProver :: Maybe String -> Prover-sorryProver reason = Prover $ \_ _ se _ -> return $ sorry reason (Just se)---- | Apply a prover only to a sub-proof, fails if the subproof doesn't exist.-focus :: ProofPath -> Prover -> Prover-focus [] prover = prover-focus path prover =- Prover $ \ctxt d _ prf ->- modifyAtPath (prover' ctxt (d + length path)) path prf- where- prover' ctxt d prf = do- se <- psInfo (root prf)- runProver prover ctxt d se prf---- | Check the proof and handle new cases using the given prover.-checkAndExtendProver :: Prover -> Prover-checkAndExtendProver prover0 = Prover $ \ctxt d se prf ->- return $ mapProofInfo snd $ checkProof ctxt (prover ctxt) d se prf- where- unhandledCase = sorry (Just "unhandled case") Nothing- prover ctxt d se =- fromMaybe unhandledCase $ runProver prover0 ctxt d se unhandledCase---- | Replace all annotated sorry steps using the given prover.-replaceSorryProver :: Prover -> Prover-replaceSorryProver prover0 = Prover prover- where- prover ctxt d _ = return . replace- where- replace prf@(LNode (ProofStep (Sorry _) (Just se)) _) =- fromMaybe prf $ runProver prover0 ctxt d se prf- replace (LNode ps cases) =- LNode ps $ M.map replace cases----- | Use the first prover that works.-firstProver :: [Prover] -> Prover-firstProver = foldr orelse failProver---- | Prover that does one contradiction step.-contradictionProver :: Prover-contradictionProver = Prover $ \ctxt d sys prf ->- runProver- (firstProver $ map oneStepProver $- (Contradiction . Just <$> contradictions ctxt sys))- ctxt d sys prf----------------------------------------------------------------------------------- Automatic Prover's---------------------------------------------------------------------------------data SolutionExtractor = CutDFS | CutBFS | CutNothing- deriving( Eq, Ord, Show, Read )--data AutoProver = AutoProver- { apHeuristic :: Heuristic- , apBound :: Maybe Int- , apCut :: SolutionExtractor- }--runAutoProver :: AutoProver -> Prover-runAutoProver (AutoProver heuristic bound cut) =- mapProverProof cutSolved $ maybe id boundProver bound autoProver- where- cutSolved = case cut of- CutDFS -> cutOnSolvedDFS- CutBFS -> cutOnSolvedBFS- CutNothing -> id-- -- | The standard automatic prover that ignores the existing proof and- -- tries to find one by itself.- autoProver :: Prover- autoProver = Prover $ \ctxt depth sys _ ->- return $ fmap (fmap Just)- $ annotateWithSystems ctxt sys- $ proveSystemDFS heuristic ctxt depth sys-- -- | Bound the depth of proofs generated by the given prover.- boundProver :: Int -> Prover -> Prover- boundProver b p = Prover $ \ctxt d se prf ->- boundProofDepth b <$> runProver p ctxt d se prf----- | The result of one pass of iterative deepening.-data IterDeepRes = NoSolution | MaybeNoSolution | Solution ProofPath--instance Monoid IterDeepRes where- mempty = NoSolution-- x@(Solution _) `mappend` _ = x- _ `mappend` y@(Solution _) = y- MaybeNoSolution `mappend` _ = MaybeNoSolution- _ `mappend` MaybeNoSolution = MaybeNoSolution- NoSolution `mappend` NoSolution = NoSolution---- | @cutOnSolvedDFS prf@ removes all other cases if an attack is found. The--- attack search is performed using a parallel DFS traversal with iterative--- deepening.------ FIXME: Note that this function may use a lot of space, as it holds onto the--- whole proof tree.-cutOnSolvedDFS :: Proof (Maybe a) -> Proof (Maybe a)-cutOnSolvedDFS prf0 =- go (4 :: Integer) $ insertPaths prf0- where- go dMax prf = case findSolved 0 prf of- NoSolution -> prf0- MaybeNoSolution -> go (2 * dMax) prf- Solution path -> extractSolved path prf0- where- findSolved d node- | d >= dMax = MaybeNoSolution- | otherwise = case node of- -- do not search in nodes that are not annotated- LNode (ProofStep _ (Nothing, _ )) _ -> NoSolution- LNode (ProofStep Solved (Just _ , path)) _ -> Solution path- LNode (ProofStep _ (Just _ , _ )) cs ->- foldMap (findSolved (succ d))- (cs `using` parTraversable nfProofMethod)-- nfProofMethod node = do- void $ rseq (psMethod $ root node)- void $ rseq (psInfo $ root node)- void $ rseq (children node)- return node-- extractSolved [] p = p- extractSolved (label:ps) (LNode pstep m) = case M.lookup label m of- Just subprf ->- LNode pstep (M.fromList [(label, extractSolved ps subprf)])- Nothing ->- error "Theory.Constraint.cutOnSolvedDFS: impossible, extractSolved failed, invalid path"---- | Search for attacks in a BFS manner.-cutOnSolvedBFS :: Proof (Maybe a) -> Proof (Maybe a)-cutOnSolvedBFS =- go (1::Int)- where- go l prf =- -- FIXME: See if that poor man's logging could be done better.- trace ("searching for attacks at depth: " ++ show l) $- case S.runState (checkLevel l prf) CompleteProof of- (_, UndeterminedProof) -> error "cutOnSolvedBFS: impossible"- (_, CompleteProof) -> prf- (_, IncompleteProof) -> go (l+1) prf- (prf', TraceFound) ->- trace ("attack found at depth: " ++ show l) prf'-- checkLevel 0 (LNode step@(ProofStep Solved (Just _)) _) =- S.put TraceFound >> return (LNode step M.empty)- checkLevel 0 prf@(LNode (ProofStep _ x) cs)- | M.null cs = return prf- | otherwise = do- st <- S.get- msg <- case st of- TraceFound -> return $ "ignored (attack exists)"- _ -> S.put IncompleteProof >> return "bound reached"- return $ LNode (ProofStep (Sorry (Just msg)) x) M.empty- checkLevel l prf@(LNode step cs)- | isNothing (psInfo step) = return prf- | otherwise = LNode step <$> traverse (checkLevel (l-1)) cs----- | @proveSystemDFS rules se@ explores all solutions of the initial--- constraint system using a depth-first-search strategy to resolve the--- non-determinism wrt. what goal to solve next. This proof can be of--- infinite depth, if the proof strategy loops.------ Use 'annotateWithSystems' to annotate the proof tree with the constraint--- systems.-proveSystemDFS :: Heuristic -> ProofContext -> Int -> System -> Proof ()-proveSystemDFS heuristic ctxt d0 sys0 =- prove d0 sys0- where- prove !depth sys =- case rankProofMethods (useHeuristic heuristic depth) ctxt sys of- [] -> node Solved M.empty- (method, (cases, _expl)):_ -> node method cases- where- node method cases =- LNode (ProofStep method ()) (M.map (prove (succ depth)) cases)------------------------------------------------------------------------------------ Pretty printing----------------------------------------------------------------------------------prettyProof :: HighlightDocument d => Proof a -> d-prettyProof = prettyProofWith (prettyProofMethod . psMethod) (const id)--prettyProofWith :: HighlightDocument d- => (ProofStep a -> d) -- ^ Make proof step pretty- -> (ProofStep a -> d -> d) -- ^ Make whole case pretty- -> Proof a -- ^ The proof to prettify- -> d-prettyProofWith prettyStep prettyCase =- ppPrf- where- ppPrf (LNode ps cs) = ppCases ps (M.toList cs)-- ppCases ps@(ProofStep Solved _) [] = prettyStep ps- ppCases ps [] = prettyCase ps (kwBy <> text " ")- <> prettyStep ps- ppCases ps [("", prf)] = prettyStep ps $-$ ppPrf prf- ppCases ps cases =- prettyStep ps $-$- (vcat $ intersperse (prettyCase ps kwNext) $ map ppCase cases) $-$- prettyCase ps kwQED-- ppCase (name, prf) = nest 2 $- (prettyCase (root prf) $ kwCase <-> text name) $-$- ppPrf prf---- | Convert a proof status to a redable string.-showProofStatus :: SystemTraceQuantifier -> ProofStatus -> String-showProofStatus ExistsNoTrace TraceFound = "falsified - found trace"-showProofStatus ExistsNoTrace CompleteProof = "verified"-showProofStatus ExistsSomeTrace CompleteProof = "falsified - no trace found"-showProofStatus ExistsSomeTrace TraceFound = "verified"-showProofStatus _ IncompleteProof = "analysis incomplete"-showProofStatus _ UndeterminedProof = "analysis undetermined"----- Derived instances-----------------------$( derive makeBinary ''ProofStep)-$( derive makeBinary ''ProofStatus)-$( derive makeBinary ''SolutionExtractor)-$( derive makeBinary ''AutoProver)--$( derive makeNFData ''ProofStep)-$( derive makeNFData ''ProofStatus)-$( derive makeNFData ''SolutionExtractor)-$( derive makeNFData ''AutoProver)--instance (Ord l, NFData l, NFData a) => NFData (LTree l a) where- rnf (LNode r m) = rnf r `seq` rnf m--instance (Ord l, Binary l, Binary a) => Binary (LTree l a) where- put (LNode r m) = put r >> put m- get = LNode <$> get <*> get
− src/Theory/Text/Parser.hs
@@ -1,668 +0,0 @@--- |--- Copyright : (c) 2010-2012 Simon Meier, Benedikt Schmidt--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : portable------ Parsing protocol theories. See the MANUAL for a high-level description of--- the syntax.-module Theory.Text.Parser (- parseOpenTheory- , parseOpenTheoryString- , parseLemma- , parseIntruderRulesDH-- -- * Cached Message Deduction Rule Variants- , intruderVariantsFile- , addMessageDeductionRuleVariants- ) where--import Prelude hiding (id, (.))--import qualified Data.ByteString.Char8 as BC-import Data.Char (isUpper, toUpper)-import Data.Foldable (asum)-import Data.Label-import qualified Data.Map as M-import Data.Monoid hiding (Last)-import qualified Data.Set as S--import Control.Applicative hiding (empty, many, optional)-import Control.Category-import Control.Monad--import Extension.Prelude (ifM)--import Text.Parsec hiding ((<|>))-import Text.PrettyPrint.Class (render)--import Paths_tamarin_prover-import System.Directory--import Term.Substitution-import Term.SubtermRule-import Theory-import Theory.Text.Parser.Token-import Theory.Tools.IntruderRules---------------------------------------------------------------------------------------- Lexing and parsing theory files and proof methods----------------------------------------------------------------------------------- | Parse a security protocol theory file.-parseOpenTheory :: [String] -- ^ Defined flags- -> FilePath -> IO OpenTheory-parseOpenTheory flags = parseFile (theory flags)---- | Parse DH intruder rules.-parseIntruderRulesDH :: FilePath -> IO [IntrRuleAC]-parseIntruderRulesDH = parseFile (setState dhMaudeSig >> many intrRule)---- | Parse a security protocol theory from a string.-parseOpenTheoryString :: [String] -- ^ Defined flags.- -> String -> Either ParseError OpenTheory-parseOpenTheoryString flags = parseFromString (theory flags)---- | Parse a lemma for an open theory from a string.-parseLemma :: String -> Either ParseError (Lemma ProofSkeleton)-parseLemma = parseFromString lemma----------------------------------------------------------------------------------- Parsing Terms----------------------------------------------------------------------------------- | Parse an lit with logical variables.-llit :: Parser LNTerm-llit = asum [freshTerm <$> freshName, pubTerm <$> pubName, varTerm <$> msgvar]---- | Lookup the arity of a non-ac symbol. Fails with a sensible error message--- if the operator is not known.-lookupNonACArity :: String -> Parser Int-lookupNonACArity op = do- maudeSig <- getState- case lookup (BC.pack op) (S.toList $ allFunctionSymbols maudeSig) of- Nothing -> fail $ "unknown operator `" ++ op ++ "'"- Just k -> return k---- | Parse an n-ary operator application for arbitrary n.-naryOpApp :: Ord l => Parser (Term l) -> Parser (Term l)-naryOpApp plit = do- op <- identifier- k <- lookupNonACArity op- ts <- parens $ if k == 1- then return <$> tupleterm plit- else commaSep (multterm plit)- let k' = length ts- when (k /= k') $- fail $ "operator `" ++ op ++"' has arity " ++ show k ++- ", but here it is used with arity " ++ show k'- return $ fAppNonAC (BC.pack op, k') ts---- | Parse a binary operator written as @op{arg1}arg2@.-binaryAlgApp :: Ord l => Parser (Term l) -> Parser (Term l)-binaryAlgApp plit = do- op <- identifier- k <- lookupNonACArity op- arg1 <- braced (tupleterm plit)- arg2 <- term plit- when (k /= 2) $ fail $- "only operators of arity 2 can be written using the `op{t1}t2' notation"- return $ fAppNonAC (BC.pack op, 2) [arg1, arg2]---- | Parse a term.-term :: Ord l => Parser (Term l) -> Parser (Term l)-term plit = asum- [ pairing <?> "pairs"- , parens (multterm plit)- , symbol "1" *> pure fAppOne- , application <?> "function application"- , nullaryApp- , plit- ]- <?> "term"- where- application = asum $ map (try . ($ plit)) [naryOpApp, binaryAlgApp]- pairing = angled (tupleterm plit)- nullaryApp = do- maudeSig <- getState- -- FIXME: This try should not be necessary.- asum [ try (symbol (BC.unpack sym)) *> pure (fApp (NonAC (sym,0)) [])- | (sym,0) <- S.toList $ allFunctionSymbols maudeSig ]---- | A left-associative sequence of exponentations.-expterm :: Ord l => Parser (Term l) -> Parser (Term l)-expterm plit = chainl1 (term plit) ((\a b -> fAppExp (a,b)) <$ opExp)---- | A left-associative sequence of multiplications.-multterm :: Ord l => Parser (Term l) -> Parser (Term l)-multterm plit = do- dh <- enableDH <$> getState- if dh -- if DH is not enabled, do not accept 'multterm's and 'expterm's- then chainl1 (expterm plit) ((\a b -> fAppMult [a,b]) <$ opMult)- else term plit---- | A right-associative sequence of tuples.-tupleterm :: Ord l => Parser (Term l) -> Parser (Term l)-tupleterm plit = chainr1 (multterm plit) ((\a b -> fAppPair (a,b)) <$ comma)---- | Parse a fact.-fact :: Ord l => Parser (Term l) -> Parser (Fact (Term l))-fact plit = try (- do multi <- option Linear (opBang *> pure Persistent)- i <- identifier- case i of- [] -> fail "empty identifier"- (c:_) | isUpper c -> return ()- | otherwise -> fail "facts must start with upper-case letters"- ts <- parens (commaSep (multterm plit))- mkProtoFact multi i ts- <?> "fact" )- where- singleTerm _ constr [t] = return $ constr t- singleTerm f _ ts = fail $ "fact '" ++ f ++ "' used with arity " ++- show (length ts) ++ " instead of arity one"-- mkProtoFact multi f = case map toUpper f of- "OUT" -> singleTerm f outFact- "IN" -> singleTerm f inFact- "KU" -> singleTerm f kuFact- "KD" -> return . Fact KDFact- "DED" -> return . Fact DedFact- "FR" -> singleTerm f freshFact- _ -> return . protoFact multi f------------------------------------------------------------------------------------ Parsing Rules----------------------------------------------------------------------------------- | Parse a "(modulo ..)" information.-modulo :: String -> Parser ()-modulo thy = parens $ symbol_ "modulo" *> symbol_ thy--moduloE, moduloAC :: Parser ()-moduloE = modulo "E"-moduloAC = modulo "AC"--{---- | Parse a typing assertion modulo E.-typeAssertions :: Parser TypingE-typeAssertions = fmap TypingE $- do try (symbols ["type", "assertions"])- optional moduloE- colon- many1 ((,) <$> (try (msgvar <* colon))- <*> ( commaSep1 (try $ multterm llit) <|>- (opMinus *> pure [])- )- )- <|> pure []--}---- | Parse a protocol rule. For the special rules 'Reveal_fresh', 'Fresh',--- 'Knows', and 'Learn' no rule is returned as the default theory already--- contains them.-protoRule :: Parser (ProtoRuleE)-protoRule = do- name <- try (symbol "rule" *> optional moduloE *> identifier <* colon)- subst <- option emptySubst letBlock- (ps,as,cs) <- genericRule- return $ apply subst $ Rule (StandRule name) ps cs as---- | Parse a let block with bottom-up application semantics.-letBlock :: Parser LNSubst-letBlock = do- toSubst <$> (symbol "let" *> many1 definition <* symbol "in")- where- toSubst = foldr1 compose . map (substFromList . return)- definition = (,) <$> (sortedLVar [LSortMsg] <* equalSign) <*> multterm llit---- | Parse an intruder rule.-intrRule :: Parser IntrRuleAC-intrRule = do- info <- try (symbol "rule" *> moduloAC *> intrInfo <* colon)- (ps,as,cs) <- genericRule- return $ Rule info ps cs as- where- intrInfo = do- name <- identifier- case name of- 'c':cname -> return $ ConstrRule (BC.pack cname)- 'd':dname -> return $ DestrRule (BC.pack dname)- _ -> fail $ "invalid intruder rule name '" ++ name ++ "'"--genericRule :: Parser ([LNFact], [LNFact], [LNFact])-genericRule =- (,,) <$> list (fact llit)- <*> ((pure [] <* symbol "-->") <|>- (symbol "--[" *> commaSep (fact llit) <* symbol "]->"))- <*> list (fact llit)--{---- | Add facts to a rule.-addFacts :: String -- ^ Command to be used: add_concs, add_prems- -> Parser (String, [LNFact])-addFacts cmd =- (,) <$> (symbol cmd *> identifier <* colon) <*> commaSep1 fact--}----------------------------------------------------------------------------------- Parsing transfer notation---------------------------------------------------------------------------------{---- | Parse an lit with strings for both constants as well as variables.-tlit :: Parser TTerm-tlit = asum- [ constTerm <$> singleQuoted identifier- , varTerm <$> identifier- ]---- | Parse a single transfer.-transfer :: Parser Transfer-transfer = do- tf <- (\l -> Transfer l Nothing Nothing) <$> identifier <* kw DOT- (do right <- kw RIGHTARROW *> identifier <* colon- desc <- transferDesc- return $ tf { tfRecv = Just (desc right) }- <|>- do right <- kw LEFTARROW *> identifier <* colon- descr <- transferDesc- (do left <- try $ identifier <* kw LEFTARROW <* colon- descl <- transferDesc- return $ tf { tfSend = Just (descr right)- , tfRecv = Just (descl left) }- <|>- do return $ tf { tfSend = Just (descr right) }- )- <|>- do left <- identifier- (do kw RIGHTARROW- (do right <- identifier <* colon- desc <- transferDesc- return $ tf { tfSend = Just (desc left)- , tfRecv = Just (desc right) }- <|>- do descl <- colon *> transferDesc- (do right <- kw RIGHTARROW *> identifier <* colon- descr <- transferDesc- return $ tf { tfSend = Just (descl left)- , tfRecv = Just (descr right) }- <|>- do return $ tf { tfSend = Just (descl left) }- )- )- <|>- do kw LEFTARROW- (do desc <- colon *> transferDesc- return $ tf { tfRecv = Just (desc left) }- <|>- do right <- identifier <* colon- desc <- transferDesc- return $ tf { tfSend = Just (desc right)- , tfRecv = Just (desc left) }- )- )- )- where- transferDesc = do- ts <- tupleterm tlit- moreConcs <- (symbol "note" *> many1 (try $ fact tlit))- <|> pure []- types <- typeAssertions- return $ \a -> TransferDesc a ts moreConcs types----- | Parse a protocol in transfer notation-transferProto :: Parser [ProtoRuleE]-transferProto = do- name <- symbol "anb-proto" *> identifier- braced (convTransferProto name <$> abbrevs <*> many1 transfer)- where- abbrevs = (symbol "let" *> many1 abbrev) <|> pure []- abbrev = (,) <$> try (identifier <* kw EQUAL) <*> multterm tlit---}----------------------------------------------------------------------------------- Parsing Standard and Guarded Formulas----------------------------------------------------------------------------------- | Parse an atom with possibly bound logical variables.-blatom :: Parser BLAtom-blatom = (fmap (fmapTerm (fmap Free))) <$> asum- [ Last <$> try (symbol "last" *> parens nodevarTerm) <?> "last atom"- , flip Action <$> try (fact llit <* opAt) <*> nodevarTerm <?> "action atom"- , Less <$> try (nodevarTerm <* opLess) <*> nodevarTerm <?> "less atom"- , EqE <$> try (multterm llit <* opEqual) <*> multterm llit <?> "term equality"- , EqE <$> (nodevarTerm <* opEqual) <*> nodevarTerm <?> "node equality"- ]- where- nodevarTerm = (lit . Var) <$> nodevar---- | Parse an atom of a formula.-fatom :: Parser LNFormula-fatom = asum- [ pure lfalse <* opLFalse- , pure ltrue <* opLTrue- , Ato <$> try blatom- , quantification- , parens iff- ]- where- quantification = do- q <- (pure forall <* opForall) <|> (pure exists <* opExists)- vs <- many1 lvar <* dot- f <- iff- return $ foldr (hinted q) f vs-- hinted :: ((String, LSort) -> LVar -> a) -> LVar -> a- hinted f v@(LVar n s _) = f (n,s) v------ | Parse a negation.-negation :: Parser LNFormula-negation = opLNot *> (Not <$> fatom) <|> fatom---- | Parse a left-associative sequence of conjunctions.-conjuncts :: Parser LNFormula-conjuncts = chainl1 negation ((.&&.) <$ opLAnd)---- | Parse a left-associative sequence of disjunctions.-disjuncts :: Parser LNFormula-disjuncts = chainl1 conjuncts ((.||.) <$ opLOr)---- | An implication.-imp :: Parser LNFormula-imp = do- lhs <- disjuncts- asum [ opImplies *> ((lhs .==>.) <$> imp)- , pure lhs ]---- | An logical equivalence.-iff :: Parser LNFormula-iff = do- lhs <- imp- asum [opLEquiv *> ((lhs .<=>.) <$> imp), pure lhs ]---- | Parse a standard formula.-standardFormula :: Parser LNFormula-standardFormula = iff---- | Parse a guarded formula using the hack of parsing a standard formula and--- converting it afterwards.------ FIXME: Write a proper parser.-guardedFormula :: Parser LNGuarded-guardedFormula = try $ do- res <- formulaToGuarded <$> standardFormula- case res of- Left d -> fail $ render d- Right gf -> return gf------------------------------------------------------------------------------------ Parsing Axioms----------------------------------------------------------------------------------- | Parse an axiom.-axiom :: Parser Axiom-axiom = Axiom <$> (symbol "axiom" *> identifier <* colon)- <*> doubleQuoted standardFormula------------------------------------------------------------------------------------ Parsing Lemmas----------------------------------------------------------------------------------- | Parse a 'LemmaAttribute'.-lemmaAttribute :: Parser LemmaAttribute-lemmaAttribute = asum- [ symbol "typing" *> pure TypingLemma- , symbol "reuse" *> pure ReuseLemma- , symbol "use_induction" *> pure InvariantLemma- ]---- | Parse a 'TraceQuantifier'.-traceQuantifier :: Parser TraceQuantifier-traceQuantifier = asum- [ symbol "all-traces" *> pure AllTraces- , symbol "exists-trace" *> pure ExistsTrace- ]---- | Parse a lemma.-lemma :: Parser (Lemma ProofSkeleton)-lemma = skeletonLemma <$> (symbol "lemma" *> optional moduloE *> identifier)- <*> (option [] $ list lemmaAttribute)- <*> (colon *> option AllTraces traceQuantifier)- <*> doubleQuoted standardFormula- <*> (proofSkeleton <|> pure (unproven ()))------------------------------------------------------------------------------------ Parsing Proofs----------------------------------------------------------------------------------- | Parse a node premise.-nodePrem :: Parser NodePrem-nodePrem = parens ((,) <$> nodevar- <*> (comma *> fmap (PremIdx . fromIntegral) natural))---- | Parse a node conclusion.-nodeConc :: Parser NodeConc-nodeConc = parens ((,) <$> nodevar- <*> (comma *> fmap (ConcIdx .fromIntegral) natural))---- | Parse a goal.-goal :: Parser Goal-goal = asum- [ premiseGoal- , actionGoal- , chainGoal- , disjSplitGoal- , eqSplitGoal- ]- where- actionGoal = do- fa <- try (fact llit <* opAt)- i <- nodevar- return $ ActionG i fa-- premiseGoal = do- (fa, v) <- try ((,) <$> fact llit <*> opRequires)- i <- nodevar- return $ PremiseG (i, v) fa-- chainGoal = ChainG <$> (try (nodeConc <* opChain)) <*> nodePrem-- disjSplitGoal = (DisjG . Disj) <$> sepBy1 guardedFormula (symbol "∥")-- eqSplitGoal = try $ do- symbol_ "split"- parens $ (SplitG . SplitId . fromIntegral) <$> natural----- | Parse a proof method.-proofMethod :: Parser ProofMethod-proofMethod = asum- [ symbol "sorry" *> pure (Sorry Nothing)- , symbol "simplify" *> pure Simplify- , symbol "solve" *> (SolveGoal <$> parens goal)- , symbol "contradiction" *> pure (Contradiction Nothing)- , symbol "induction" *> pure Induction- ]---- | Parse a proof skeleton.-proofSkeleton :: Parser ProofSkeleton-proofSkeleton =- solvedProof <|> finalProof <|> interProof- where- solvedProof =- symbol "SOLVED" *> pure (LNode (ProofStep Solved ()) M.empty)-- finalProof = do- method <- symbol "by" *> proofMethod- return (LNode (ProofStep method ()) M.empty)-- interProof = do- method <- proofMethod- cases <- (sepBy oneCase (symbol "next") <* symbol "qed") <|>- ((return . (,) "") <$> proofSkeleton )- return (LNode (ProofStep method ()) (M.fromList cases))-- oneCase = (,) <$> (symbol "case" *> identifier) <*> proofSkeleton----------------------------------------------------------------------------------- Parsing Signatures----------------------------------------------------------------------------------- | Builtin signatures.-builtins :: Parser ()-builtins =- symbol "builtins" *> colon *> commaSep1 builtinTheory *> pure ()- where- extendSig msig = modifyState (`mappend` msig)- builtinTheory = asum- [ try (symbol "diffie-hellman")- *> extendSig dhMaudeSig- , try (symbol "symmetric-encryption")- *> extendSig symEncMaudeSig- , try (symbol "asymmetric-encryption")- *> extendSig asymEncMaudeSig- , try (symbol "signing")- *> extendSig signatureMaudeSig- , symbol "hashing"- *> extendSig hashMaudeSig- ]--functions :: Parser ()-functions =- symbol "functions" *> colon *> commaSep1 functionSymbol *> pure ()- where- functionSymbol = do- f <- BC.pack <$> identifier <* opSlash- k <- fromIntegral <$> natural- sig <- getState- case lookup f (S.toList $ allFunctionSymbols sig) of- Just k' | k' /= k ->- fail $ "conflicting arities " ++- show k' ++ " and " ++ show k ++- " for `" ++ BC.unpack f- _ -> setState (addFunctionSymbol (f,k) sig)--equations :: Parser ()-equations =- symbol "equations" *> colon *> commaSep1 equation *> pure ()- where- equation = do- rrule <- RRule <$> term llit <*> (equalSign *> term llit)- case rRuleToStRule rrule of- Just str ->- modifyState (addStRule str)- Nothing ->- fail $ "Not a subterm rule: " ++ show rrule----------------------------------------------------------------------------------- Parsing Theories------------------------------------------------------------------------------------ | Parse a theory.-theory :: [String] -- ^ Defined flags.- -> Parser OpenTheory-theory flags0 = do- symbol_ "theory"- thyId <- identifier- symbol_ "begin"- *> addItems (S.fromList flags0) (set thyName thyId defaultOpenTheory)- <* symbol "end"- where- addItems :: S.Set String -> OpenTheory -> Parser OpenTheory- addItems flags thy = asum- [ do builtins- msig <- getState- addItems flags $ set (sigpMaudeSig . thySignature) msig thy- , do functions- msig <- getState- addItems flags $ set (sigpMaudeSig . thySignature) msig thy- , do equations- msig <- getState- addItems flags $ set (sigpMaudeSig . thySignature) msig thy--- , do thy' <- foldM liftedAddProtoRule thy =<< transferProto--- addItems flags thy'- , do thy' <- liftedAddAxiom thy =<< axiom- addItems flags thy'- , do thy' <- liftedAddLemma thy =<< lemma- addItems flags thy'- , do ru <- protoRule- thy' <- liftedAddProtoRule thy ru- addItems flags thy'- , do r <- intrRule- addItems flags (addIntrRuleACs [r] thy)- , do c <- formalComment- addItems flags (addFormalComment c thy)- , do ifdef flags thy- , do define flags thy- , do return thy- ]-- define :: S.Set String -> OpenTheory -> Parser OpenTheory- define flags thy = do- flag <- try (symbol "#define") *> identifier- addItems (S.insert flag flags) thy-- ifdef :: S.Set String -> OpenTheory -> Parser OpenTheory- ifdef flags thy = do- flag <- symbol_ "#ifdef" *> identifier- thy' <- addItems flags thy- symbol_ "#endif"- if flag `S.member` flags- then addItems flags thy'- else addItems flags thy-- liftedAddProtoRule thy ru = case addProtoRule ru thy of- Just thy' -> return thy'- Nothing -> fail $ "duplicate rule: " ++ render (prettyRuleName ru)-- liftedAddLemma thy lem = case addLemma lem thy of- Just thy' -> return thy'- Nothing -> fail $ "duplicate lemma: " ++ get lName lem-- liftedAddAxiom thy ax = case addAxiom ax thy of- Just thy' -> return thy'- Nothing -> fail $ "duplicate axiom: " ++ get axName ax------------------------------------------------------------------------------------ Message deduction variants cached in files----------------------------------------------------------------------------------- | The name of the intruder variants file.-intruderVariantsFile :: FilePath-intruderVariantsFile = "intruder_variants_dh.spthy"---- | Add the variants of the message deduction rule. Uses the cached version--- of the @"intruder_variants_dh.spthy"@ file for the variants of the message--- deduction rules for Diffie-Hellman exponentiation.-addMessageDeductionRuleVariants :: OpenTheory -> IO OpenTheory-addMessageDeductionRuleVariants thy0- | enableDH msig = do- variantsFile <- getDataFileName intruderVariantsFile- ifM (doesFileExist variantsFile)- (do dhVariants <- parseIntruderRulesDH variantsFile- return $ addIntrRuleACs dhVariants thy- )- (error $ "could not find intruder message deduction theory '"- ++ variantsFile ++ "'")- | otherwise = return thy- where- msig = get (sigpMaudeSig . thySignature) thy0- rules = subtermIntruderRules msig ++ specialIntruderRules- thy = addIntrRuleACs rules thy0
− src/Theory/Text/Parser/Token.hs
@@ -1,398 +0,0 @@-{-# LANGUAGE TupleSections #-}--- |--- Copyright : (c) 2010-2012 Simon Meier, Benedikt Schmidt--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : portable------ Tokenizing infrastructure-module Theory.Text.Parser.Token (- -- * Symbols- symbol- , symbol_- , dot- , comma- , colon-- , natural- , naturalSubscript-- -- ** Formal comments- , formalComment-- -- * Identifiers and Variables- , identifier- , indexedIdentifier-- , freshName- , pubName-- , sortedLVar- , lvar- , msgvar- , nodevar-- -- * Operators- , opExp- , opMult-- , opEqual- , opLess- , opAt- , opForall- , opExists- , opImplies- , opLEquiv- , opLAnd- , opLOr- , opLNot- , opLFalse- , opLTrue-- , opRequires- , opChain-- -- ** Pseudo operators- , equalSign- , opSharp- , opBang- , opSlash- , opMinus- , opLeftarrow- , opRightarrow- , opLongleftarrow- , opLongrightarrow-- -- * Parentheses/quoting- , braced- , parens- , angled- , brackets- , singleQuoted- , doubleQuoted-- -- * List parsing- , commaSep- , commaSep1- , list-- -- * Basic Parsing- , Parser- , parseFile- , parseFromString- ) where--import Prelude hiding (id, (.))--import Data.Foldable (asum)-import Data.List (foldl')--import Control.Applicative hiding (empty, many, optional)-import Control.Category-import Control.Monad--import Text.Parsec hiding ((<|>))-import qualified Text.Parsec.Token as T--import Theory---------------------------------------------------------------------------------------- Parser----------------------------------------------------------------------------------- | A parser for a stream of tokens.-type Parser a = Parsec String MaudeSig a---- Use Parsec's support for defining token parsers.-spthy :: T.TokenParser MaudeSig-spthy =- T.makeTokenParser spthyStyle- where- spthyStyle = T.LanguageDef- { T.commentStart = "/*"- , T.commentEnd = "*/"- , T.commentLine = "//"- , T.nestedComments = True- , T.identStart = alphaNum- , T.identLetter = alphaNum <|> oneOf "_"- , T.reservedNames = ["in","let","rule"]- , T.opStart = oneOf ":!$%&*+./<=>?@\\^|-"- , T.opLetter = oneOf ":!$%&*+./<=>?@\\^|-"- , T.reservedOpNames= []- , T.caseSensitive = True- }---- | Parse a file.-parseFile :: Parser a -> FilePath -> IO a-parseFile parser f = do- s <- readFile f- case runParser (T.whiteSpace spthy *> parser) minimalMaudeSig f s of- Right p -> return p- Left err -> error $ show err---- | Run a given parser on a given string.-parseFromString :: Parser a -> String -> Either ParseError a-parseFromString parser =- runParser (T.whiteSpace spthy *> parser) minimalMaudeSig dummySource- where- dummySource = "<interactive>"----- Token parsers--------------------- | Parse a symbol.-symbol :: String -> Parser String-symbol sym = try (T.symbol spthy sym) <?> ("\"" ++ sym ++ "\"")---- | Parse a symbol without returning the parsed string.-symbol_ :: String -> Parser ()-symbol_ = void . symbol---- | Between braces.-braced :: Parser a -> Parser a-braced = T.braces spthy---- | Between brackets.-brackets :: Parser a -> Parser a-brackets = T.brackets spthy---- | Between parentheses.-parens :: Parser a -> Parser a-parens = T.parens spthy---- | Between angular brackets.-angled :: Parser a -> Parser a-angled = T.angles spthy---- | Between single quotes.-singleQuoted :: Parser a -> Parser a-singleQuoted = between (symbol "'") (symbol "'")---- | Between double quotes.-doubleQuoted :: Parser a -> Parser a-doubleQuoted = between (symbol "\"") (symbol "\"")---- | A dot @.@.-dot :: Parser ()-dot = void $ T.dot spthy---- | A comma @,@.-comma :: Parser ()-comma = void $ T.comma spthy---- | A colon @:@.-colon :: Parser ()-colon = void $ T.colon spthy---- | Parse an natural.-natural :: Parser Integer-natural = T.natural spthy---- | Parse a Unicode-subscripted natural number.-naturalSubscript :: Parser Integer-naturalSubscript = T.lexeme spthy $ do- digits <- many1 (oneOf "₀₁₂₃₄₅₆₇₈₉")- let n = foldl' (\x d -> 10*x + subscriptDigitToInteger d) 0 digits- seq n (return n)- where- subscriptDigitToInteger d = toInteger $ fromEnum d - fromEnum '₀'---- | A comma separated list of elements.-commaSep :: Parser a -> Parser [a]-commaSep = T.commaSep spthy---- | A comma separated non-empty list of elements.-commaSep1 :: Parser a -> Parser [a]-commaSep1 = T.commaSep1 spthy---- | Parse a list of items '[' item ',' ... ',' item ']'-list :: Parser a -> Parser [a]-list = brackets . commaSep---- | A formal comment; i.e., (header, body)-formalComment :: Parser (String, String)-formalComment = T.lexeme spthy $ do- header <- try (many1 letter <* string "{*")- body <- many bodyChar <* string "*}"- return (header, body)- where- bodyChar = try $ do- c <- anyChar- case c of- '\\' -> char '\\' <|> char '*'- '*' -> mzero- _ -> return c---- Identifiers and Variables--------------------------------- | Parse an identifier as a string-identifier :: Parser String-identifier = T.identifier spthy---- | Parse an identifier possibly indexed with a number.-indexedIdentifier :: Parser (String, Integer)-indexedIdentifier = do- (,) <$> identifier- <*> option 0 (try (dot *> (fromIntegral <$> natural)))---- | Parse a logical variable with the given sorts allowed.-sortedLVar :: [LSort] -> Parser LVar-sortedLVar ss =- asum $ map (try . mkSuffixParser) ss ++ map mkPrefixParser ss- where- mkSuffixParser s = do- (n, i) <- indexedIdentifier <* colon- symbol_ (sortSuffix s)- return (LVar n s i)-- mkPrefixParser s = do- case s of- LSortMsg -> pure ()- LSortPub -> void $ char '$'- LSortFresh -> void $ char '~'- LSortNode -> void $ char '#'- LSortMSet -> void $ char '%'- (n, i) <- indexedIdentifier- return (LVar n s i)---- | An arbitrary logical variable.-lvar :: Parser LVar-lvar = sortedLVar [minBound..]---- | Parse a non-node variable.-msgvar :: Parser LVar-msgvar = sortedLVar [LSortFresh, LSortPub, LSortMsg, LSortMSet]---- | Parse a graph node variable.-nodevar :: Parser NodeId-nodevar = asum- [ sortedLVar [LSortNode]- , (\(n, i) -> LVar n LSortNode i) <$> indexedIdentifier ]- <?> "timepoint variable"---- | Parse a literal fresh name, e.g., @~'n'@.-freshName :: Parser String-freshName = try (symbol "~" *> singleQuoted identifier)---- | Parse a literal public name, e.g., @'n'@.-pubName :: Parser String-pubName = singleQuoted identifier----- Term Operators----------------- | The exponentiation operator @^@.-opExp :: Parser ()-opExp = symbol_ "^"---- | The multiplication operator @*@.-opMult :: Parser ()-opMult = symbol_ "*"---- | The timepoint comparison operator @<@.-opLess :: Parser ()-opLess = symbol_ "<"---- | The action-at-timepoint operator \@.-opAt :: Parser ()-opAt = symbol_ "@"---- | The equality operator @=@.-opEqual :: Parser ()-opEqual = symbol_ "="---- | The logical-forall operator @All@ or @∀@.-opForall :: Parser ()-opForall = symbol_ "All" <|> symbol_ "∀"---- | The logical-exists operator @Ex@ or @∃@.-opExists :: Parser ()-opExists = symbol_ "Ex" <|> symbol_ "∃"---- | The logical-implies operator @==>@.-opImplies :: Parser ()-opImplies = symbol_ "==>" <|> symbol_ "⇒"---- | The logical-equivalence operator @<=>@.-opLEquiv :: Parser ()-opLEquiv = symbol_ "<=>" <|> symbol_ "⇔"---- | The logical-and operator @&@ or @∧@.-opLAnd :: Parser ()-opLAnd = symbol_ "&" <|> symbol_ "∧"---- | The logical-or operator @|@ or @∨@.-opLOr :: Parser ()-opLOr = symbol_ "|" <|> symbol_ "∨"---- | The logical not operator @not@ or @¬@.-opLNot :: Parser ()-opLNot = symbol_ "¬" <|> symbol_ "not"---- | A logical false, @F@ or @⊥@.-opLFalse :: Parser ()-opLFalse = symbol_ "⊥" <|> T.reserved spthy "F"---- | A logical false, @T@ or @⊥@.-opLTrue :: Parser ()-opLTrue = symbol_ "⊤" <|> T.reserved spthy "T"---- Operators for constraints--------------------------------- | The requires-a-premise operator, @▶ subscript-idx@.-opRequires :: Parser PremIdx-opRequires = (PremIdx . fromIntegral) <$> (symbol "▶" *> naturalSubscript)---- | The chain operator @~~>@.-opChain :: Parser ()-opChain = symbol_ "~~>"----- Pseudo operators (to be replaced by usage of proper tokens)------------------------------------------------------------------- | The equal sign @=@.-equalSign :: Parser ()-equalSign = symbol_ "="---- | The slash operator @/@.-opSlash :: Parser ()-opSlash = symbol_ "/"---- | The bang operator @!@.-opBang :: Parser ()-opBang = symbol_ "!"---- | The sharp operator @#@.-opSharp :: Parser ()-opSharp = symbol_ "#"---- | The minus operator @-@.-opMinus :: Parser ()-opMinus = symbol_ "-"---- | The leftarrow operator @<--@.-opLeftarrow :: Parser ()-opLeftarrow = symbol_ "<-"---- | The rightarrow operator @-->@.-opRightarrow :: Parser ()-opRightarrow = symbol_ "->"---- | The longleftarrow operator @<--@.-opLongleftarrow :: Parser ()-opLongleftarrow = symbol_ "<--"---- | The longrightarrow operator @-->@.-opLongrightarrow :: Parser ()-opLongrightarrow = symbol_ "-->"
− src/Theory/Text/Parser/UnitTests.hs
@@ -1,91 +0,0 @@--- |--- Copyright : (c) 2012 Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>------ Unit tests for checking that all examples parse properly.-module Theory.Text.Parser.UnitTests (-- testParseFile- , testParseDirectory- ) where--import Test.HUnit--import Control.Basics--import System.Directory-import System.FilePath--import Theory-import Theory.Text.Parser-import Theory.Text.Pretty (render)---- | Test wether a given file exists, can be parsed, and can still be parsed--- after being pretty printed.-testParseFile :: Maybe (FilePath, Prover)- -- ^ Path to maude and prover for testing whether proof parsing- -- works properly.- -> FilePath- -- ^ File on which to test parsing (and proving)- -> Test-testParseFile optionalProver inpFile = TestLabel inpFile $ TestCase $ do- thyString <- readFile inpFile- thy0 <- parse "original file:" thyString- -- add proofs and pretty print closed theory, if desired- (thy, thyPretty) <- case optionalProver of- Nothing ->- return (thy0, prettyOpenTheory thy0)- Just (maudePath, prover) -> do- closedThy <- proveTheory prover <$> closeTheory maudePath thy0- return $ ( normalizeTheory $ openTheory closedThy- , prettyClosedTheory closedThy)- thy' <- parse "pretty printed theory:" (render thyPretty)- unless (thy == thy') $ do- let (diff1, diff2) =- unzip $ dropWhile (uncurry (==)) $ zip (show thy) (show thy')- assertFailure $ unlines- [ "Original theory", "", render (prettyOpenTheory thy), ""- , "Pretty printed and parsed" , "", render (prettyOpenTheory thy'), ""- , "Original theory (diff)", "", indent diff1, ""- , "Pretty printed and parsed (diff)" , "", indent diff2, "", "DIFFER"- ]- return ()- where- indent = unlines . map (' ' :) . lines-- parse msg str = case parseOpenTheoryString [] str of- Left err -> do assertFailure $ withLineNumbers $ indent $ show err- return (error "testParseFile: dead code")- Right thy -> normalizeTheory <$> addMessageDeductionRuleVariants thy- where- withLineNumbers err =- unlines $ zipWith (\i l -> nr (show i) ++ l) [(1::Int)..] ls- ++ ["", "Parse error when parsing the " ++ msg, err]- where- ls = lines str- n = length (show (length ls))- nr i = replicate (1 + max 0 (n - length i)) ' ' ++ i ++ ": "---- | Create the test whether 'testParseFile' succeeds on all @*.spthy@ files--- in a given directory and all its subdirectories of depth n.-testParseDirectory :: (FilePath -> Test) -- ^ Test creation function.- -> Int -- ^ Maximal depth of traversal.- -> FilePath -- ^ Starting directory.- -> IO [Test]-testParseDirectory mkTest n dir- | n < 0 = return []- | otherwise = do- rawContents <- getDirectoryContents dir- let contents = [ dir </> content- | content <- rawContents- , content /= ".", content /= ".." ]- subDirs <- filterM doesDirectoryExist contents- innerTests <- mapM (testParseDirectory mkTest (n - 1)) subDirs- let tests = [ file- | file <- contents, takeExtension file == ".spthy" ]- mapM_ (putStrLn . (" peparing: " ++)) tests- return $ map mkTest tests ++ map TestList innerTests--
− src/Theory/Text/Pretty.hs
@@ -1,130 +0,0 @@--- |--- Copyright : (c) 2011 Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : portable------ General support for pretty printing theories.-module Theory.Text.Pretty (- -- * General highlighters- module Text.PrettyPrint.Highlight-- -- * Additional combinators- , vsep- , fsepList-- -- * Comments- , lineComment- , multiComment-- , lineComment_- , multiComment_-- -- * Keywords- , kwTheoryHeader- , kwEnd- , kwModulo- , kwBy- , kwCase- , kwNext- , kwQED- , kwLemma- , kwAxiom-- -- ** Composed forms- , kwRuleModulo- , kwInstanceModulo- , kwVariantsModulo- , kwTypesModulo-- -- * Operators- , opProvides- , opRequires- , opAction- , opPath- , opLess- , opEqual- , opDedBefore- , opEdge-- ) where--import Text.PrettyPrint.Highlight------------------------------------------------------------------------------------ Additional combinators----------------------------------------------------------------------------------- | Vertically separate a list of documents by empty lines.-vsep :: Document d => [d] -> d-vsep = foldr ($--$) emptyDoc---- | Pretty print a list of values as a comma-separated list wrapped in--- paragraph mode.-fsepList :: Document d => (a -> d) -> [a] -> d-fsepList pp = fsep . punctuate comma . map pp------------------------------------------------------------------------------------ Comments---------------------------------------------------------------------------------lineComment :: HighlightDocument d => d -> d-lineComment d = comment $ text "//" <-> d--lineComment_ :: HighlightDocument d => String -> d-lineComment_ = lineComment . text--multiComment :: HighlightDocument d => d -> d-multiComment d = comment $ fsep [text "/*", d, text "*/"]--multiComment_ :: HighlightDocument d => [String] -> d-multiComment_ ls = comment $ fsep [text "/*", vcat $ map text ls, text "*/"]----------------------------------------------------------------------------------- Keywords---------------------------------------------------------------------------------kwTheoryHeader :: HighlightDocument d => d -> d-kwTheoryHeader name = keyword_ "theory" <-> name <-> keyword_ "begin"--kwEnd, kwBy, kwCase, kwNext, kwQED, kwAxiom, kwLemma :: HighlightDocument d => d-kwEnd = keyword_ "end"-kwBy = keyword_ "by"-kwCase = keyword_ "case"-kwNext = keyword_ "next"-kwQED = keyword_ "qed"-kwAxiom = keyword_ "axiom"-kwLemma = keyword_ "lemma"--kwModulo :: HighlightDocument d- => String -- ^ What- -> String -- ^ modulo theory- -> d-kwModulo what thy = keyword_ what <-> parens (keyword_ "modulo" <-> text thy)--kwRuleModulo, kwInstanceModulo, kwTypesModulo, kwVariantsModulo- :: HighlightDocument d => String -> d-kwRuleModulo = kwModulo "rule"-kwInstanceModulo = kwModulo "instance"-kwTypesModulo = kwModulo "type assertions"-kwVariantsModulo = kwModulo "variants"------------------------------------------------------------------------------------ Operators---------------------------------------------------------------------------------opProvides, opRequires, opAction, opPath, opLess, opEqual, opDedBefore, opEdge- :: HighlightDocument d => d-opProvides = operator_ ":>"-opRequires = operator_ "<:"-opAction = operator_ "@"-opPath = operator_ ">+>"-opLess = operator_ "<"-opEqual = operator_ "="-opDedBefore = operator_ "--|"-opEdge = operator_ ">->"-
− src/Theory/Tools/AbstractInterpretation.hs
@@ -1,147 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2012 Benedikt Schmidt & Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>------ Abstract intepretation for partial evaluation of multiset rewriting--- systems.-module Theory.Tools.AbstractInterpretation (- -- * Combinator to define abstract interpretations- interpretAbstractly-- -- ** Actual interpretations- , EvaluationStyle(..)- , partialEvaluation-- ) where--import Debug.Trace--import Control.Basics-import Control.Monad.Bind-import Control.Monad.Reader--import Data.Label-import Data.List-import qualified Data.Set as S-import Data.Traversable (traverse)--import Term.Substitution-import Theory.Model-import Theory.Text.Pretty------------------------------------------------------------------------------------ Abstract enough versions of builtin rules for computing------------------------------------------------------------------------------------ | Higher-order combinator to construct abstract interpreters.-interpretAbstractly- :: (Eq s, HasFrees i, Apply i)- => ([Equal LNFact] -> [LNSubstVFresh])- -- ^ Unification of equalities over facts. We assume that facts with- -- different tags are never unified.- -> s -- ^ Initial abstract state.- -> (LNFact -> s -> s) -- ^ Add a fact to the abstract state- -> (s -> [LNFact]) -- ^ Facts of a state.- -> [Rule i]- -- ^ Multiset rewriting rules to apply abstractly.- -> [(s, [Rule i])]- -- ^ Sequence of abstract states and refined versions of all given- -- multiset rewriting rules.-interpretAbstractly unifyFactEqs initState addFact stateFacts rus =- go st0- where- st0 = addFact (freshFact (varTerm (LVar "z" LSortFresh 0))) $- addFact (inFact (varTerm (LVar "z" LSortMsg 0))) $- initState-- -- Repeatedly refine all rules and add all their conclusions until the- -- state doesn't change anymore.- go st =- (st, rus') : if st == st' then [] else go st'- where- rus' = concatMap refineRule rus- st' = foldl' (flip addFact) st $ concatMap (get rConcs) rus'-- -- Refine a rule in the context of an abstract state: for all premise- -- to state facts combinations, try to solve the corresponding- -- E-unification problem. If successful, return the rule with the- -- unifier applied.- refineRule ru = (`evalFreshT` avoid ru) $ do- eqs <- forM (get rPrems ru) $ \prem -> msum $ do- fa <- stateFacts st- guard (factTag prem == factTag fa)- -- we compute a list of 'FreshT []' actions for the outer msum- return (Equal prem <$> rename fa)- subst <- msum $ freshToFree <$> unifyFactEqs eqs- return $ apply subst ru---- | How to report on performing a partial evaluation.-data EvaluationStyle = Silent | Summary | Tracing---- | Concrete partial evaluator activated with flag: --partial-evaluation-partialEvaluation :: EvaluationStyle- -> [ProtoRuleE] -> WithMaude (S.Set LNFact, [ProtoRuleE])-partialEvaluation evalStyle ruEs = reader $ \hnd ->- consumeEvaluation $ interpretAbstractly- ((`runReader` hnd) . unifyLNFactEqs) -- FIXME: Use E-unification here- S.empty- (S.insert . absFact)- S.toList- ruEs- where- consumeEvaluation [] = error "partialEvaluation: impossible"- consumeEvaluation ((st0, rus0) : rest0) =- go (0 :: Int) st0 rus0 rest0- where- go _ st rus [] =- ( st- , nubBy eqModuloFreshnessNoAC $ -- remove duplicates- map ((`evalFresh` nothingUsed) . rename) rus- )- go i st _ ((st', rus') : rest) =- withTrace (go (i + 1) st' rus' rest)- where- incDesc = " partial evaluation: step " ++ show i ++ " added " ++- show (S.size st' - S.size st) ++ " facts"- withTrace = case evalStyle of- Silent -> id- Summary -> trace incDesc- Tracing -> trace $ incDesc ++ "\n\n" ++- ( render $ nest 2 $ numbered' $ map prettyLNFact $- S.toList $ st' `S.difference` st ) ++ "\n"--- -- NOTE: We should use an abstract state that identifies all variables at- -- the same position provided they have the same sort.- absFact :: LNFact -> LNFact- absFact fa = case fa of- Fact OutFact _ -> outFact (varTerm (LVar "z" LSortMsg 0))- Fact tag ts -> Fact tag $ evalAbstraction $ traverse absTerm ts- where- evalAbstraction = (`evalBind` noBindings) . (`evalFreshT` nothingUsed)-- absTerm t = case viewTerm t of- Lit (Con _) -> pure t- FApp (sym@(NonAC (_f,_k))) ts- -> fApp sym <$> traverse absTerm ts- -- | "p" `isPrefixOf` f -> FApp sym <$> traverse absTerm ts- _ -> importBinding mkVar t (varName t)- where- mkVar name idx = varTerm (LVar name (sortOfLNTerm t) idx)- varName (viewTerm -> Lit (Var v)) = lvarName v- varName _ = "z"--{- FIXME: Implement---- | Perform a simple propagation of sorts at the fact level.-propagateSorts :: [ProtoRuleE]- -> WithMaude (M.Map FactTag [LSort], [ProtoRuleE])-propagateSorts ruEs = reader $ \hnd ->---}
− src/Theory/Tools/EquationStore.hs
@@ -1,566 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TupleSections #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt, Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Benedikt Schmidt <beschmi@gmail.com>--- Portability : GHC only------ Support for reasoning with and about disjunctions of substitutions.-module Theory.Tools.EquationStore (- -- * Equations- SplitId(..)-- , EqStore(..)- , emptyEqStore- , eqsSubst- , eqsConj-- -- ** Equalitiy constraint conjunctions- , falseEqConstrConj-- -- ** Queries- , eqsIsFalse--- -- ** Adding equalities- , addEqs- , addRuleVariants- , addDisj-- -- ** Case splitting- , performSplit-- , splits- , splitSize- , splitExists-- -- * Simplification- , simp- , simpDisjunction-- -- ** Pretty printing- , prettyEqStore-) where--import Logic.Connectives-import Term.Unification-import Theory.Text.Pretty--import Control.Monad.Fresh-import Control.Monad.Reader-import Extension.Prelude-import Utils.Misc--import Debug.Trace.Ignore--import Control.Basics-import Control.DeepSeq-import Control.Monad.State hiding (get, modify, put)-import qualified Control.Monad.State as MS--import Data.Binary-import Data.DeriveTH-import qualified Data.Foldable as F-import Data.List-import Data.Maybe-import qualified Data.Set as S-import Extension.Data.Label hiding (for, get)-import qualified Extension.Data.Label as L-import Extension.Data.Monoid----------------------------------------------------------------------------------- Equation Store ------------------------------------------------------------------------------------- | Index of disjunction in equation store-newtype SplitId = SplitId { unSplitId :: Integer }- deriving( Eq, Ord, Show, Enum, Binary, NFData, HasFrees )---- FIXME: Make comment parse.------ The semantics of an equation store--- > EqStore sigma_free--- > [ [sigma_i1,..,sigma_ik_i] | i <- [1..l] ]--- where sigma_free = {t1/x1, .., tk/xk} is--- > (x1 = t1 /\ .. /\ xk = tk)--- > /\_{i in [1..l]}--- > ([|sigma_i1|] \/ .. \/ [|sigma_ik_1|] \/ [|mtinfo_i|]--- where @[|{t_1/x_1,..,t_l/x_l}|] = EX vars(t1,..,tl). x_1 = t1 /\ .. /\ x_l = t_l@.--- Note that the 'LVar's in the range of a substitution are interpreted as--- fresh variables, i.e., different by construction from the x_i which are--- free variables.------ The variables in the domain of the substitutions sigma_ij and all--- variables in sigma_free are free (usually globally existentially quantified).--- We use Conj [] as a normal form to denote True and Conj [Disj []]--- as a normal form to denote False.--- We say a variable @x@ is constrained by a disjunction if there is a substition--- @s@ in the disjunction with @x `elem` dom s@.-data EqStore = EqStore {- _eqsSubst :: LNSubst- , _eqsConj :: Conj (SplitId, S.Set LNSubstVFresh)- , _eqsNextSplitId :: SplitId- }- deriving( Eq, Ord )--$(mkLabels [''EqStore])---- | @emptyEqStore@ is the empty equation store.-emptyEqStore :: EqStore-emptyEqStore = EqStore emptySubst (Conj []) (SplitId 0)---- | @True@ iff the 'EqStore' is contradictory.-eqsIsFalse :: EqStore -> Bool-eqsIsFalse = any ((S.empty == ) . snd) . getConj . L.get eqsConj---- | The false conjunction. It is always identified with split number -1.-falseEqConstrConj :: Conj (SplitId, S.Set (LNSubstVFresh))-falseEqConstrConj = Conj [ (SplitId (-1), S.empty) ]----- Instances---------------instance Apply SplitId where- apply _ = id--instance HasFrees EqStore where- foldFrees f (EqStore subst substs nextSplitId) =- foldFrees f subst <> foldFrees f substs <> foldFrees f nextSplitId- mapFrees f (EqStore subst substs nextSplitId) =- EqStore <$> mapFrees f subst- <*> mapFrees f substs- <*> mapFrees f nextSplitId------ Equation Store--------------------------------------------------------------------------- | We use the empty set (disjunction) to denote false.-falseDisj :: S.Set LNSubstVFresh-falseDisj = S.empty----- Dealing with equations--------------------------------------------------------------------------- | Returns the list of all @SplitId@s valid for the given equation store--- sorted by the size of the disjunctions.-splits :: EqStore -> [SplitId]-splits eqs = map fst $ nub $ sortOn snd- [ (idx, S.size conj) | (idx, conj) <- getConj $ L.get eqsConj eqs ]---- | Returns 'True' if the 'SplitId' is valid.-splitExists :: EqStore -> SplitId -> Bool-splitExists eqs = isJust . splitSize eqs---- | Returns the number of cases for a given 'SplitId'.-splitSize :: EqStore -> SplitId -> Maybe Int-splitSize eqs sid =- (S.size . snd) <$> (find ((sid ==) . fst) $ getConj $ L.get eqsConj $ eqs)---- | Add a disjunction to the equation store at the beginning-addDisj :: EqStore -> (S.Set LNSubstVFresh) -> (EqStore, SplitId)-addDisj eqStore disj =- ( modify eqsConj ((Conj [(sid, disj)]) `mappend`)- $ modify eqsNextSplitId succ- $ eqStore- , sid- )- where- sid = L.get eqsNextSplitId eqStore---- | @performSplit eqs i@ performs a case-split on the first disjunction--- with the given 'SplitId'.-performSplit :: EqStore -> SplitId -> Maybe [EqStore]-performSplit eqStore idx =- case break ((idx ==) . fst) (getConj $ L.get eqsConj eqStore) of- (_, []) -> Nothing- (before, (_, disj):after) -> Just $- mkNewEqStore before after <$> S.toList disj- where- mkNewEqStore before after subst =- fst $ addDisj (set eqsConj (Conj (before ++ after)) eqStore)- (S.singleton subst)---- | Add a list of term equalities to the equation store. Returns the split--- identifier of the disjunction in resulting equation store.-addEqs :: MonadFresh m- => MaudeHandle -> [Equal LNTerm] -> EqStore -> m (EqStore, Maybe SplitId)-addEqs hnd eqs0 eqStore =- case unifyLNTermFactored eqs `runReader` hnd of- (_, []) ->- return (set eqsConj falseEqConstrConj eqStore, Nothing)- (subst, [substFresh]) | substFresh == emptySubstVFresh ->- return (applyEqStore hnd subst eqStore, Nothing)- (subst, substs) -> do- let (eqStore', sid) = addDisj (applyEqStore hnd subst eqStore)- (S.fromList substs)- return (eqStore', Just sid)- {-- case splitStrat of- SplitLater ->- return [ addDisj (applyEqStore hnd subst eqStore) (S.fromList substs) ]- SplitNow ->- addEqsAC (modify eqsSubst (compose subst) eqStore)- <$> simpDisjunction hnd (const False) (Disj substs)- -}- where- eqs = apply (L.get eqsSubst eqStore) $ trace (unlines ["addEqs: ", show eqs0]) $ eqs0- {-- addEqsAC eqSt (sfree, Nothing) = [ applyEqStore hnd sfree eqSt ]- addEqsAC eqSt (sfree, Just disj) =- fromMaybe (error "addEqsSplit: impossible, splitAtPos failed")- (splitAtPos (applyEqStore hnd sfree (addDisj eqSt (S.fromList disj))) 0)--}---- | Apply a substitution to an equation store and bring resulting equations into--- normal form again by using unification.-applyEqStore :: MaudeHandle -> LNSubst -> EqStore -> EqStore-applyEqStore hnd asubst eqStore- | dom asubst `intersect` varsRange asubst /= [] || trace (show ("applyEqStore", asubst, eqStore)) False- = error $ "applyEqStore: dom and vrange not disjoint for `"++show asubst++"'"- | otherwise- = modify eqsConj (fmap (second (S.fromList . concatMap applyBound . S.toList))) $- set eqsSubst newsubst eqStore- where- newsubst = asubst `compose` L.get eqsSubst eqStore- applyBound s = map (restrictVFresh (varsRange newsubst ++ domVFresh s)) $- (`runReader` hnd) $ unifyLNTerm- [ Equal (apply newsubst (varTerm lv)) t- | let slist = substToListVFresh s,- -- variables in the range are fresh, so we have to rename- -- them away from all other variables in unification problem- -- NOTE: these variables never enter the global context- let ran = renameAvoiding (map snd slist)- (domVFresh s ++ varsRange newsubst),- (lv,t) <- zip (map fst slist) ran- ]--{- NOTES for @applyEqStore tau@ to a fresh substitution sigma:-[ FIXME: extend explanation to multiple unifiers ]-Let dom(sigma) = x1,..,xk, vrange(sigma) = y1, .. yl, vrange(tau) = z1,..,zn-Fresh substitution denotes formula- exists #y1, .., #yl. x1 = t1 /\ .. /\ xk = tk-for variables #yi that do not clash with xi and zi [renameAwayFrom]-and with vars(ti) `subsetOf` [#y1, .. #yl].-We apply tau with vrange(tau) = z1,..,zn to the formula to obtain- exists ##y1, .., ##yl. tau(x1) = t1 /\ .. /\ tau(xk) = tk-unification then yields a lemma- forall xi zi #yi.- tau(x1) = t1 /\ .. /\ tau(xk) = tk- <-> exists vars(s1,..sm). x1 = .. /\ z1 = .. /\ #y1 = ..-So we have- exists #y1, .., #yl.- exists vars(s1,..sm). x1 = .. /\ z1 = .. /\ #y1 = ..-<=>- exists vars(s1,..sm). x1 = .. /\ z1 = ..- /\ (exists #y1, .., #yl. #y1 = ..)-<=> [restric]- exists vars(s1,..sm). x1 = .. /\ z1 = .. /\ True--}---- | Add the given rule variants.-addRuleVariants :: Disj LNSubstVFresh -> EqStore -> (EqStore, SplitId)-addRuleVariants (Disj substs) eqStore- | dom freeSubst `intersect` concatMap domVFresh substs /= []- = error $ "addRuleVariants: Nonempty intersection between domain of variants and free substitution. "- ++"This case has not been implemented, add rule variants earlier."- | otherwise = addDisj eqStore (S.fromList substs)- where- freeSubst = L.get eqsSubst eqStore---{---- | Return the set of variables that is constrained by disjunction at give position.-constrainedVarsPos :: EqStore -> Int -> [LVar]-constrainedVarsPos eqStore k- | k < length conj = frees (conj!!k)- | otherwise = []- where- conj = getConj . L.get eqsConj $ eqStore--}---- Simplifying disjunctions--------------------------------------------------------------------------- | Simplify given disjunction via EqStore simplification. Obtains fresh--- names for variables from the underlying 'MonadFresh'.-simpDisjunction :: MonadFresh m- => MaudeHandle- -> (LNSubstVFresh -> Bool)- -> Disj LNSubstVFresh- -> m (LNSubst, Maybe [LNSubstVFresh])-simpDisjunction hnd isContr disj0 = do- eqStore' <- simp hnd isContr eqStore- return (L.get eqsSubst eqStore', wrap $ L.get eqsConj eqStore')- where- eqStore = fst $ addDisj emptyEqStore (S.fromList $ getDisj $ disj0)- wrap (Conj []) = Nothing- wrap (Conj [(_, disj)]) = Just $ S.toList disj- wrap conj =- error ("simplifyDisjunction: imposible, unexpected conjunction `"- ++ show conj ++ "'")----- Simplification--------------------------------------------------------------------------- | @simp eqStore@ simplifies the equation store.-simp :: MonadFresh m => MaudeHandle -> (LNSubstVFresh -> Bool) -> EqStore -> m EqStore-simp hnd isContr eqStore =- execStateT (whileTrue (simp1 hnd isContr))- (trace (show ("eqStore", eqStore)) eqStore)----- | @simp1@ tries to execute one simplification step--- for the equation store. It returns @True@ if--- the equation store was modified.-simp1 :: MonadFresh m => MaudeHandle -> (LNSubstVFresh -> Bool) -> StateT EqStore m Bool-simp1 hnd isContr = do- s <- MS.get- if eqsIsFalse s- then return False- else do- b1 <- simpMinimize isContr- b2 <- simpRemoveRenamings- b3 <- simpEmptyDisj- b4 <- foreachDisj hnd simpSingleton- b5 <- foreachDisj hnd simpAbstractSortedVar- b6 <- foreachDisj hnd simpIdentify- b7 <- foreachDisj hnd simpAbstractFun- b8 <- foreachDisj hnd simpAbstractName- (trace (show ("simp:", [b1, b2, b3, b4, b5, b6, b7, b8]))) $- return $ (or [b1, b2, b3, b4, b5, b6, b7, b8])----- | Remove variable renamings in fresh substitutions.-simpRemoveRenamings :: MonadFresh m => StateT EqStore m Bool-simpRemoveRenamings = do- conj <- gets (L.get eqsConj)- if F.any (S.foldl' (\b subst -> b || domVFresh subst /= domVFresh (removeRenamings subst)) False . snd) conj- then modM eqsConj (fmap (second $ S.map removeRenamings)) >> return True- else return False----- | If empty disjunction is found, the whole conjunct--- can be simplified to False.-simpEmptyDisj :: MonadFresh m => StateT EqStore m Bool-simpEmptyDisj = do- conj <- getM eqsConj- if (F.any ((== falseDisj) . snd) conj && conj /= falseEqConstrConj)- then eqsConj =: falseEqConstrConj >> return True- else return False----- | If there is a singleton disjunction, it can be--- composed with the free substitution.-simpSingleton :: MonadFresh m- => [LNSubstVFresh]- -> m (Maybe (Maybe LNSubst, [S.Set LNSubstVFresh]))-simpSingleton [subst0] = do- subst <- freshToFree subst0- return (Just (Just subst, []))-simpSingleton _ = return Nothing----- | If all substitutions @si@ map a variable @v@ to terms with the same--- outermost function symbol @f@, then they all contain the common factor--- @{v |-> f(x1,..,xk)}@ for fresh variables xi and we can replace--- @x |-> ..@ by @{x1 |-> ti1, x2 |-> ti2, ..}@ in all substitutions @si@.-simpAbstractFun :: MonadFresh m- => [LNSubstVFresh]- -> m (Maybe (Maybe LNSubst, [S.Set LNSubstVFresh]))-simpAbstractFun [] = return Nothing-simpAbstractFun (subst:others) = case commonOperators of- [] -> return Nothing- -- abstract all arguments- (v, o, argss@(args:_)):_ | all ((==length args) . length) argss -> do- fvars <- mapM (\_ -> freshLVar "x" LSortMsg) args- let substs' = zipWith (abstractAll v fvars) (subst:others) argss- fsubst = substFromList [(v, fApp o (map varTerm fvars))]- return $ Just (Just fsubst, [S.fromList substs'])- -- abstract first two arguments- (v, o@(AC _), argss):_ -> do- fv1 <- freshLVar "x" LSortMsg- fv2 <- freshLVar "x" LSortMsg- let substs' = zipWith (abstractTwo o v fv1 fv2) (subst:others) argss- fsubst = substFromList [(v, fApp o (map varTerm [fv1,fv2]))]- return $ Just (Just fsubst, [S.fromList substs'])- (_, _ ,_):_ ->- error "simpAbstract: impossible, invalid arities or List operator encountered."- where- commonOperators = do- (v, viewTerm -> FApp o args) <- substToListVFresh subst- let images = map (\s -> imageOfVFresh s v) others- argss = [ args' | Just (viewTerm -> FApp o' args') <- images, o' == o ]- guard (length argss == length others)- return (v, o, args:argss)-- abstractAll v freshVars s args = substFromListVFresh $- filter ((/= v) . fst) (substToListVFresh s) ++ zip freshVars args-- abstractTwo o v fv1 fv2 s args = substFromListVFresh $- filter ((/= v) . fst) (substToListVFresh s) ++ newMappings args- where- newMappings [] =- error "simpAbstract: impossible, AC symbols must have arity >= 2."- newMappings [a1,a2] = [(fv1, a1), (fv2, a2)]- -- here we always abstract from left to right and do not- -- take advantage of the AC property of o- newMappings (a:as) = [(fv1, a), (fv2, fApp o as)]----- | If all substitutions @si@ map a variable @v@ to the same name @n@,--- then they all contain the common factor--- @{v |-> n}@ and we can remove @{v -> n} from all substitutions @si@-simpAbstractName :: MonadFresh m- => [LNSubstVFresh]- -> m (Maybe (Maybe LNSubst, [S.Set LNSubstVFresh]))-simpAbstractName [] = return Nothing-simpAbstractName (subst:others) = case commonNames of- [] -> return Nothing- (v, c):_ ->- return $ Just (Just $ substFromList [(v, c)]- , [S.fromList (map (\s -> restrictVFresh (delete v (domVFresh s)) s) (subst:others))])- where- commonNames = do- (v, c@(viewTerm -> Lit (Con _))) <- substToListVFresh subst- let images = map (\s -> imageOfVFresh s v) others- guard (length images == length [ () | Just c' <- images, c' == c])- return (v, c)----- | If all substitutions @si@ map a variable @v@ to variables @xi@ of the same--- sort @s@ then they all contain the common factor--- @{v |-> y}@ for a fresh variable of sort @s@--- and we can replace @{v -> xi}@ by @{y -> xi} in all substitutions @si@-simpAbstractSortedVar :: MonadFresh m- => [LNSubstVFresh]- -> m (Maybe (Maybe LNSubst, [S.Set LNSubstVFresh]))-simpAbstractSortedVar [] = return Nothing-simpAbstractSortedVar (subst:others) = case commonSortedVar of- [] -> return Nothing- (v, s, lvs):_ -> do- fv <- freshLVar (lvarName v) s- return $ Just (Just $ substFromList [(v, varTerm fv)]- , [S.fromList (zipWith (replaceMapping v fv) lvs (subst:others))])- where- commonSortedVar = do- (v, (viewTerm -> Lit (Var lx))) <- substToListVFresh subst- guard (sortCompare (lvarSort v) (lvarSort lx) == Just GT)- let images = map (\s -> imageOfVFresh s v) others- -- FIXME: could be generalized to choose topsort s of all images if s < sortOf v- -- could also be generalized to terms of a given sort- goodImages = [ ly | Just (viewTerm -> Lit (Var ly)) <- images, lvarSort lx == lvarSort ly]- guard (length images == length goodImages)- return (v, lvarSort lx, (lx:goodImages))- replaceMapping v fv lv sigma =- substFromListVFresh $ (filter ((/=v) . fst) $ substToListVFresh sigma) ++ [(fv, varTerm lv)]---- | If all substitutions @si@ map two variables @x@ and @y@ to identical terms @ti@,--- then they all contain the common factor @{x |-> y} for a fresh variable @z@--- and we can remove @{x |-> ti}@ from all @si@.-simpIdentify :: MonadFresh m- => [LNSubstVFresh]- -> m (Maybe (Maybe LNSubst, [S.Set LNSubstVFresh]))-simpIdentify [] = return Nothing-simpIdentify (subst:others) = case equalImgPairs of- [] -> return Nothing- ((v,v'):_) -> do- let (vkeep, vremove) = case sortCompare (lvarSort v) (lvarSort v') of- Just GT -> (v', v)- Just _ -> (v, v')- Nothing -> error $ "EquationStore.simpIdentify: impossible, variables with incomparable sorts: "- ++ show v ++" and "++ show v'- return $ Just (Just (substFromList [(vremove, varTerm vkeep)]),- [S.fromList (map (removeMappings [vkeep]) (subst:others))])- where- equalImgPairs = do- (v,t) <- substToListVFresh subst- (v', t') <- substToListVFresh subst- guard (t == t' && v < v' && all (agrees_on v v') others)- return (v,v')- agrees_on v v' s =- imageOfVFresh s v == imageOfVFresh s v' && isJust (imageOfVFresh s v)- removeMappings vs s = restrictVFresh (domVFresh s \\ vs) s----- | Simplify by removing substitutions that occur twice in a disjunct.--- We could generalize this function by using AC-equality or subsumption.-simpMinimize :: MonadFresh m => (LNSubstVFresh -> Bool) -> StateT EqStore m Bool-simpMinimize isContr = do- conj <- MS.gets (L.get eqsConj)- if F.any (F.any check . snd) conj- then MS.modify (set eqsConj (fmap (second minimize) conj)) >> return True- else return False- where- minimize substs- | emptySubstVFresh `S.member` substs = S.singleton emptySubstVFresh- | otherwise = S.filter (not . isContr) substs-- check subst = subst == emptySubstVFresh || isContr subst----- | Traverse disjunctions and execute @f@ until it returns--- @Just (mfreeSubst, disjs)@.--- Then the @disjs@ is inserted at the current position, if @mfreeSubst@ is--- @Just freesubst@, then it is applied to the equation store. @True@ is--- returned if any modifications took place.-foreachDisj :: MonadFresh m- => MaudeHandle- -> ([LNSubstVFresh] -> m (Maybe (Maybe LNSubst, [S.Set LNSubstVFresh])))- -> StateT EqStore m Bool-foreachDisj hnd f =- go [] =<< gets (getConj . L.get eqsConj)- where- go _ [] = return False- go lefts ((idx,d):rights) = do- b <- lift $ f (S.toList d)- case b of- Nothing -> go ((idx,d):lefts) rights- Just (msubst, disjs) -> do- eqsConj =: Conj (reverse lefts ++ ((,) idx <$> disjs) ++ rights)- maybe (return ()) (\s -> MS.modify (applyEqStore hnd s)) msubst- return True----------------------------------------------------------------------------------- Pretty printing----------------------------------------------------------------------------------- | Pretty print an 'EqStore'.-prettyEqStore :: HighlightDocument d => EqStore -> d-prettyEqStore eqs@(EqStore subst (Conj disjs) _nextSplitId) = vcat $- [if eqsIsFalse eqs then text "CONTRADICTORY" else emptyDoc] ++- map combine- [ ("subst", vcat $ prettySubst (text . show) (text . show) subst)- , ("conj", vcat $ map ppDisj disjs)- ]- where- combine (header, d) = fsep [keyword_ header <> colon, nest 2 d]- ppDisj (idx, substs) =- text (show (unSplitId idx)) <-> numbered' conjs- where- conjs = map ppConj (S.toList substs)- ppConj = vcat . map prettyEq . substToListVFresh- prettyEq (a,b) =- prettyNTerm (lit (Var a)) $$ nest (6::Int) (opEqual <-> prettyNTerm b)------ Derived and delayed instances-----------------------------------instance Show EqStore where- show = render . prettyEqStore--$( derive makeBinary ''EqStore)-$( derive makeNFData ''EqStore)
− src/Theory/Tools/InjectiveFactInstances.hs
@@ -1,71 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--- |--- Copyright : (c) 2012 Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : portable------ Computate an under-approximation to the set of all facts with unique--- instances, i.e., fact whose instances never occur more than once in a--- state. We use this information to reason about protocols that exploit--- exclusivity of linear facts.-module Theory.Tools.InjectiveFactInstances (-- -- * Computing injective fact instances.- simpleInjectiveFactInstances- ) where--import Extension.Prelude (sortednub)--import Control.Applicative-import Control.Monad.Fresh-import Data.Label-import qualified Data.Set as S-import Safe (headMay)--import Theory.Model---- | Compute a simple under-approximation to the set of facts with injective--- instances. A fact-tag is has injective instances, if there is no state of--- the protocol with more than one instance with the same term as a first--- argument of the fact-tag.------ We compute the under-approximation by checking that--- (1) the fact-tag is linear,--- (2) every introduction of such a fact-tag is protected by a Fr-fact of the--- first term, and--- (3) every rule has at most one copy of this fact-tag in the conlcusion and--- the first term arguments agree.------ We exclude facts that are not copied in a rule, as they are already handled--- properly by the naive backwards reasoning.-simpleInjectiveFactInstances :: [ProtoRuleE] -> S.Set FactTag-simpleInjectiveFactInstances rules = S.fromList $ do- tag <- candidates- guard (all (guardedSingletonCopy tag) rules)- return tag- where- candidates = sortednub $ do- ru <- rules- tag <- factTag <$> get rConcs ru- guard $ (factTagMultiplicity tag == Linear)- && (tag `elem` (factTag <$> get rPrems ru))- return tag-- guardedSingletonCopy tag ru =- length copies <= 1 && all guardedCopy copies- where- prems = get rPrems ru- copies = filter ((tag ==) . factTag) (get rConcs ru)- firstTerm = headMay . factTerms-- -- True if there is a first term and a premise guarding it- guardedCopy faConc = case firstTerm faConc of- Nothing -> False- Just tConc -> freshFact tConc `elem` prems || guardedInPrems tConc-- -- True if there is a premise with the same tag and first term- guardedInPrems tConc = (`any` prems) $ \faPrem ->- factTag faPrem == tag && maybe False (tConc ==) (firstTerm faPrem)-
− src/Theory/Tools/IntruderRules.hs
@@ -1,205 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE ViewPatterns #-}-{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}- -- spurious warnings for view patterns--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt--- License : GPL v3 (see LICENSE)------ Maintainer : Benedikt Schmidt <beschmi@gmail.com>--- Portability : GHC only----module Theory.Tools.IntruderRules (- subtermIntruderRules- , dhIntruderRules- , specialIntruderRules- ) where--import Control.Basics-import Control.Monad.Reader--import Data.List-import qualified Data.Set as S--import Extension.Data.Label--import Utils.Misc--import Term.Maude.Signature-import Term.Narrowing.Variants.Compute-import Term.Rewriting.Norm-import Term.SubtermRule-import Term.Positions--import Theory.Model------ Variants of intruder deduction rules----------------------------------------------------------------------------------------------------------------------------------------------------------- Special Intruder rules---------------------------------------------------------------------------------{--These are the special intruder that are always included.--rule (modulo AC) coerce:- [ KD( f_, x ) ] --[ KU( f_, x) ]-> [ KU( f_, x ) ]--rule (modulo AC) pub:- [ ] --[ KU( f_, $x) ]-> [ KU( f_, $x ) ]--rule (modulo AC) gen_fresh:- [ Fr( ~x ) ] --[ KU( 'noexp', ~x ) ]-> [ KU( 'noexp', ~x ) ]--rule (modulo AC) isend:- [ KU( f_, x) ] --[ K(x) ]-> [ In(x) ]--rule (modulo AC) irecv:- [ Out( x) ] --> [ KD( 'exp', x) ]---}--- | @specialIntruderRules@ returns the special intruder rules that are--- included independently of the message theory-specialIntruderRules :: [IntrRuleAC]-specialIntruderRules =- [ kuRule CoerceRule [kdFact x_var] (x_var)- , kuRule PubConstrRule [] (x_pub_var)- , kuRule FreshConstrRule [Fact FreshFact [x_fresh_var]] (x_fresh_var)- , Rule ISendRule [kuFact x_var] [Fact InFact [x_var]] [kLogFact x_var]- , Rule IRecvRule [Fact OutFact [x_var]] [Fact KDFact [x_var]] []- ]- where- kuRule name prems t = Rule name prems [kuFact t] [kuFact t]-- x_var = varTerm (LVar "x" LSortMsg 0)- x_pub_var = varTerm (LVar "x" LSortPub 0)- x_fresh_var = varTerm (LVar "x" LSortFresh 0)------------------------------------------------------------------------------------ Subterm Intruder theory----------------------------------------------------------------------------------- | @destuctionRules st@ returns the destruction rules for the given--- subterm rule @st@-destructionRules :: StRule -> [IntrRuleAC]-destructionRules (StRule lhs@(viewTerm -> FApp (NonAC (f,_)) _) (RhsPosition pos)) =- go [] lhs pos- where- rhs = lhs `atPos` pos- go _ _ [] = []- -- term already in premises- go _ (viewTerm -> FApp _ _) (_:[]) = []- go uprems (viewTerm -> FApp _ as) (i:p) =- irule ++ go uprems' t' p- where- uprems' = uprems++[ t | (j, t) <- zip [0..] as, i /= j ]- t' = as!!i- irule = if (t' /= rhs && rhs `notElem` uprems')- then [ Rule (DestrRule f)- ((kdFact t'):(map kuFact uprems'))- [kdFact rhs] [] ]- else []- go _ (viewTerm -> Lit _) (_:_) =- error "IntruderRules.destructionRules: impossible, position invalid"--destructionRules _ = []---- | Simple removal of subsumed rules for auto-generated subterm intruder rules.-minimizeIntruderRules :: [IntrRuleAC] -> [IntrRuleAC]-minimizeIntruderRules rules =- go [] rules- where- go checked [] = reverse checked- go checked (r@(Rule _ prems concs _):unchecked) = go checked' unchecked- where- checked' = if any (\(Rule _ prems' concs' _)- -> concs' == concs && prems' `subsetOf` prems)- (checked++unchecked)- then checked- else r:checked---- | @subtermIntruderRules maudeSig@ returns the set of intruder rules for--- the subterm (not Xor, DH, and MSet) part of the given signature.-subtermIntruderRules :: MaudeSig -> [IntrRuleAC]-subtermIntruderRules maudeSig =- minimizeIntruderRules $ concatMap destructionRules (S.toList $ stRules maudeSig)- ++ constructionRules (functionSymbols maudeSig)---- | @constructionRules fSig@ returns the construction rules for the given--- function signature @fSig@-constructionRules :: FunSig -> [IntrRuleAC]-constructionRules fSig =- [ createRule s k | (s,k) <- S.toList fSig ]- where- createRule s k = Rule (ConstrRule s) (map kuFact vars) [concfact] [concfact]- where vars = take k [ varTerm (LVar "x" LSortMsg i) | i<- [0..] ]- m = fApp (NonAC (s,k)) vars- concfact = kuFact m------------------------------------------------------------------------------------ Diffie-Hellman Intruder Rules----------------------------------------------------------------------------------- | @dhIntruderRules@ computes the intruder rules for DH-dhIntruderRules :: WithMaude [IntrRuleAC]-dhIntruderRules = reader $ \hnd -> minimizeIntruderRules $- [ expRule ConstrRule kuFact return- , invRule ConstrRule kuFact return- ] ++- concatMap (variantsIntruder hnd)- [ expRule DestrRule kdFact (const [])- , invRule DestrRule kdFact (const [])- ]- where- x_var_0 = varTerm (LVar "x" LSortMsg 0)- x_var_1 = varTerm (LVar "x" LSortMsg 1)-- expRule mkInfo kudFact mkAction =- Rule (mkInfo expSymString) [bfact, efact] [concfact] (mkAction concfact)- where- bfact = kudFact x_var_0- efact = kuFact x_var_1- conc = fAppExp (x_var_0, x_var_1)- concfact = kudFact conc-- invRule mkInfo kudFact mkAction =- Rule (mkInfo invSymString) [bfact] [concfact] (mkAction concfact)- where- bfact = kudFact x_var_0- conc = fAppInv x_var_0- concfact = kudFact conc----- | @variantsIntruder mh irule@ computes the deconstruction-variants--- of a given intruder rule @irule@-variantsIntruder :: MaudeHandle -> IntrRuleAC -> [IntrRuleAC]-variantsIntruder hnd ru = do- let ruleTerms = concatMap factTerms- (get rPrems ru++get rConcs ru++get rActs ru)- fsigma <- computeVariants (fAppList ruleTerms) `runReader` hnd- let sigma = freshToFree fsigma `evalFreshAvoiding` ruleTerms- ruvariant = normRule' (apply sigma ru) `runReader` hnd- guard (frees (get rConcs ruvariant) /= [] &&- -- ground terms are already deducible by applying construction rules- ruvariant /= ru &&- -- this is a construction rule- (get rConcs ruvariant) \\ (get rPrems ruvariant) /= []- -- The conclusion is included in the premises- )-- case concatMap factTerms $ get rConcs ruvariant of- [viewTerm -> FApp (AC Mult) _] ->- fail "Rules with product conclusion are redundant"- _ -> return ruvariant---- | @normRule irule@ computes the normal form of @irule@-normRule' :: IntrRuleAC -> WithMaude IntrRuleAC-normRule' (Rule i ps cs as) = reader $ \hnd ->- let normFactTerms = map (fmap (\t -> norm' t `runReader` hnd)) in- Rule i (normFactTerms ps) (normFactTerms cs) (normFactTerms as)
− src/Theory/Tools/LoopBreakers.hs
@@ -1,80 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--- |--- Copyright : (c) 2012 Simon Meier--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : portable------ Computate the loop-breakers in the premise-conclusion graph of a set of--- multiset rewriting rules.-module Theory.Tools.LoopBreakers (-- -- * Computing loop breakers for solving premises- useAutoLoopBreakersAC- ) where--import Control.Applicative-import Control.Monad.Fresh-import Control.Monad.Reader--import Data.DAG.Simple--import Theory.Model----- | An over-approximation of the dependency of solving premises. An element--- @((fromRu, fromPrem), (toRu, toPrem))@ denotes that solving the premise--- @(fromRu,fromPrem)@ might lead to a case where the premise @(toRu, toPrem)@--- is open.-premSolvingRelAC :: (a -> [(PremIdx, LNFact)]) -- ^ Enumerate premises- -> (a -> [(ConcIdx, LNFact)]) -- ^ Enumerate conclusions- -> (a -> [LNSubstVFresh]) -- ^ Enumerate variants- -> [a] -- ^ Base carrier- -> WithMaude (Relation (a, PremIdx))-premSolvingRelAC ePrems eConcs eVariants rules = reader $ \hnd -> do- (toRu, from) <- dataflowRelAC hnd- (toPrem, _) <- ePrems toRu- return (from, (toRu, toPrem))- where- -- An over-approxmiation of the dataflow relation. An element @(fromRu,- -- (toRu, toPrem))@ denotes that there is a conclusion of @fromRu@- -- unifying with the premise @(toRu, toPrem)@.- dataflowRelAC hnd = do- ruFrom <- rules- ruTo <- rules- (premIdx, premFa0) <- ePrems ruTo- guard $ or $ do- premFa <- instances ruTo premFa0- concFa <- instances ruFrom =<< (snd <$> eConcs ruFrom)- let concFaFresh = rename concFa `evalFresh` avoid premFa- return $ (`runReader` hnd) (unifiableLNFacts concFaFresh premFa)- return (ruFrom, (ruTo, premIdx))-- instances ru fa = do- subst <- eVariants ru- return (apply (subst `freshToFreeAvoiding` fa) fa)----- | Replace all loop-breaker information with loop-breakers computed--- automatically from the dataflow relation 'dataflowRelAC'.-useAutoLoopBreakersAC- :: Ord a- => (a -> [(PremIdx, LNFact)]) -- ^ Enumerate premises- -> (a -> [(ConcIdx, LNFact)]) -- ^ Enumerate conclusions- -> (a -> [LNSubstVFresh]) -- ^ Enumerate variants- -> ([PremIdx] -> a -> a) -- ^ Add annotation- -> [a] -- ^ Original rules- -> WithMaude ([a], Relation (a, PremIdx), [(a, PremIdx)])- -- ^ Annotated rules and the premise solving relation-useAutoLoopBreakersAC ePrems eConcs eVariants addAnn rules =- reader $ \hnd ->- let solveRel = (`runReader` hnd) $- premSolvingRelAC ePrems eConcs eVariants rules- breakers = dfsLoopBreakers $ solveRel- in ( do ru <- rules- return (addAnn [ u | (ru', u) <- breakers, ru == ru' ] ru)- , solveRel- , breakers- )-
− src/Theory/Tools/RuleVariants.hs
@@ -1,100 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2010-2012 Benedikt Schmidt--- License : GPL v3 (see LICENSE)------ Maintainer : Benedikt Schmidt <beschmi@gmail.com>--- Portability : GHC only------ Variants of protocol rules.-module Theory.Tools.RuleVariants where--import Term.Narrowing.Variants-import Term.Rewriting.Norm-import Theory.Model-import Theory.Tools.EquationStore--import Extension.Prelude-import Logic.Connectives--import Control.Applicative-import Control.Monad.Bind-import Control.Monad.Reader-import qualified Control.Monad.Trans.PreciseFresh as Precise--import qualified Data.Map as M-import qualified Data.Set as S-import Data.Traversable (traverse)--import Debug.Trace.Ignore---- Variants of protocol rules--------------------------------------------------------------------------- | Compute the variants of a protocol rule.--- 1. Abstract away terms in facts with variables.--- 2. Compute variants of RHSs of equations.--- 3. Apply variant substitutions to equations--- to obtain DNF of equations.--- 4. Simplify rule.-variantsProtoRule :: MaudeHandle -> ProtoRuleE -> ProtoRuleAC-variantsProtoRule hnd ru@(Rule ri prems0 concs0 acts0) =- -- rename rule to decrease variable indices- (`Precise.evalFresh` Precise.nothingUsed) . renamePrecise $ convertRule `evalFreshAvoiding` ru- where- convertRule = do- (abstrPsCsAs, bindings) <- abstrRule- let eqsAbstr = map swap (M.toList bindings)- abstractedTerms = map snd eqsAbstr- abstractionSubst = substFromList eqsAbstr- variantSubsts = computeVariants (fAppList abstractedTerms) `runReader` hnd- substs = [ restrictVFresh (frees abstrPsCsAs) $- removeRenamings $ ((`runReader` hnd) . normSubstVFresh') $- composeVFresh vsubst abstractionSubst- | vsubst <- variantSubsts ]-- case substs of- [] -> error $ "variantsProtoRule: rule has no variants `"++show ru++"'"- _ -> do- -- x <- return (emptySubst, Just substs) --- x <- simpDisjunction hnd (const False) (Disj substs)- case trace (show ("SIMP",abstractedTerms,- "abstr", abstrPsCsAs,- "substs", substs,- "simpSubsts:", x)) x of- -- the variants can be simplified to a single case- (commonSubst, Nothing) ->- return $ makeRule abstrPsCsAs commonSubst trueDisj- (commonSubst, Just freshSubsts) ->- return $ makeRule abstrPsCsAs commonSubst freshSubsts-- abstrRule = (`runBindT` noBindings) $ do- -- first import all vars into binding to obtain nicer names- mapM_ abstrTerm [ varTerm v | v <- frees (prems0, concs0, acts0) ]- (,,) <$> mapM abstrFact prems0- <*> mapM abstrFact concs0- <*> mapM abstrFact acts0-- irreducible = irreducibleFunctionSymbols (mhMaudeSig hnd)- abstrFact = traverse abstrTerm- abstrTerm (viewTerm -> FApp (NonAC o) args) | o `S.member` irreducible =- fAppNonAC o <$> mapM abstrTerm args- abstrTerm t = do- at :: LNTerm <- varTerm <$> importBinding (`LVar` sortOfLNTerm t) t (getHint t)- return at- where getHint (viewTerm -> Lit (Var v)) = lvarName v- getHint _ = "z"-- makeRule (ps, cs, as) subst freshSubsts0 =- Rule (ProtoRuleACInfo ri (Disj freshSubsts) []) prems concs acts- where prems = apply subst ps- concs = apply subst cs- acts = apply subst as- freshSubsts = map (restrictVFresh (frees (prems, concs, acts))) freshSubsts0-- trueDisj = [ emptySubstVFresh ]
− src/Theory/Tools/Wellformedness.hs
@@ -1,519 +0,0 @@-{-# LANGUAGE ViewPatterns #-}--- |--- Copyright : (c) 2010-2012 Simon Meier & Benedikt Schmidt--- License : GPL v3 (see LICENSE)------ Maintainer : Simon Meier <iridcode@gmail.com>--- Portability : GHC only------ Wellformedness checks for intruder variants, protocol rules, and--- properties.------ The following checks are/should be performed--- (FIXME: compare the list below to what is really implemented.)------ [protocol rules]------ 1. no fresh names in rule. (protocol cond. 1)--- ==> freshNamesReport------ 2. no Out or K facts in premises. (protocol cond. 2)--- ==> factReports------ 3. no Fr, In, or K facts in conclusions. (protocol cond. 3)--- ==> factReports------ 4. vars(rhs) subset of vars(lhs) u V_Pub--- ==> multRestrictedReport------ 5. lhs does not contain reducible function symbols (*-restricted (a))--- ==> multRestrictedReport------ 6. rhs does not contain * (*-restricted (b))--- ==> multRestrictedReport------ 7. all facts are used with the same arity.------ 8. fr, in, and out, facts are used with arity 1.------ 9. fr facts are used with a variable of sort msg or sort fresh------ 10. fresh facts of the same rule contain different variables. [TODO]------ 11. no protocol fact uses a reserved name =>--- [TODO] change parser to ensure this and pretty printer to show this.------ [security properties]------ 1. all facts occur with the same arity in the action of some--- protocol rule.------ 2. no node variable is used in a message position and vice versa.-------module Theory.Tools.Wellformedness (-- -- * Wellformedness checking- WfErrorReport- , checkWellformedness- , noteWellformedness-- , prettyWfErrorReport- ) where--import Prelude hiding (id, (.))--import Control.Basics-import Control.Category-import Data.Char-import Data.Generics.Uniplate.Data (universeBi)-import Data.Label-import Data.List-import Data.Maybe-import Data.Monoid (mappend, mempty)-import qualified Data.Set as S-import Data.Traversable (traverse)--import Control.Monad.Bind--import Extension.Prelude-import Term.LTerm-import Term.Maude.Signature-import Theory-import Theory.Text.Pretty----------------------------------------------------------------------------------- Types for error reports---------------------------------------------------------------------------------type Topic = String-type WfError = (Topic, Doc)-type WfErrorReport = [WfError]--prettyWfErrorReport :: WfErrorReport -> Doc-prettyWfErrorReport =- vcat . intersperse (text "") . map ppTopic . groupOn fst- where- ppTopic [] = error "prettyWfErrorReport: groupOn returned empty list"- ppTopic errs@((topic,_):_) =- text topic <> colon $-$- (nest 2 . vcat . intersperse (text "") $ map snd errs)------------------------------------------------------------------------------------ Utilities----------------------------------------------------------------------------------- | All protocol rules of a theory.--- thyProtoRules :: OpenTheory ->-thyProtoRules :: OpenTheory -> [ProtoRuleE]-thyProtoRules thy = [ ru | RuleItem ru <- get thyItems thy ]---- | Lower-case a string.-lowerCase :: String -> String-lowerCase = map toLower---- | Pretty-print a comma, separated list of 'LVar's.-prettyVarList :: Document d => [LVar] -> d-prettyVarList = fsep . punctuate comma . map prettyLVar---- | Pretty-print a comma, separated list of 'LNTerms's.-prettyLNTermList :: Document d => [LNTerm] -> d-prettyLNTermList = fsep . punctuate comma . map prettyLNTerm---- | Wrap strings at word boundaries.-wrappedText :: Document d => String -> d-wrappedText = fsep . map text . words---- | Clashes-clashesOn :: (Ord b, Ord c)- => (a -> b) -- ^ This projection- -> (a -> c) -- ^ must determine this projection.- -> [a] -> [[a]]-clashesOn f g xs = do- grp <- groupOn f $ sortOn f xs- guard (length (sortednubOn g grp) >= 2)- return grp---- | Nice quoting.-quote :: String -> String-quote cs = '`' : cs ++ "'"----------------------------------------------------------------------------------- Checks------------------------------------------------------------------------------------ | Check that the protocol rules are well-formed.-sortsClashCheck :: HasFrees t => String -> t -> WfErrorReport-sortsClashCheck info t = case clashesOn removeSort id $ frees t of- [] -> []- cs -> return $- ( "sorts"- , text info $-$ (nest 2 $ numbered' $ map prettyVarList cs)- )- where- removeSort lv = (lowerCase (lvarName lv), lvarIdx lv)---- | Report on sort clashes.-ruleSortsReport :: OpenTheory -> WfErrorReport-ruleSortsReport thy = do- ru <- thyProtoRules thy- sortsClashCheck ("rule " ++ quote (showRuleCaseName ru) ++- " clashing sorts, casings, or multiplicities:") ru---- | Report on fresh names.-freshNamesReport :: OpenTheory -> WfErrorReport-freshNamesReport thy = do- ru <- thyProtoRules thy- case filter ((LSortFresh ==) . sortOfName) $ universeBi ru of- [] -> []- names -> return $ (,) "fresh names" $ fsep $- text ( "rule " ++ quote (showRuleCaseName ru) ++ ": " ++- "fresh names are not allowed in rule:" )- : punctuate comma (map (nest 2 . text . show) names)---- | Report on capitalization of public names.-publicNamesReport :: OpenTheory -> WfErrorReport-publicNamesReport thy =- case findClashes publicNames of- [] -> []- clashes -> return $ (,) topic $ numbered' $- map (nest 2 . fsep . punctuate comma . map ppRuleAndName) clashes- where- topic = "public names with mismatching capitalization"- publicNames = do- ru <- thyProtoRules thy- (,) (showRuleCaseName ru) <$>- (filter ((LSortPub ==) . sortOfName) $ universeBi ru)- findClashes = clashesOn (map toLower . show . snd) (show . snd)- ppRuleAndName (ruName, pub) =- text $ "rule " ++ show ruName ++ " name " ++ show pub---- | Check whether a rule has unbound variables.-unboundCheck :: HasFrees i => String -> Rule i -> WfErrorReport-unboundCheck info ru- | null unboundVars = []- | otherwise = return $- ( "unbound"- , text info $-$ (nest 2 $ prettyVarList unboundVars) )- where- boundVars = S.fromList $ frees (get rPrems ru)- unboundVars = do- v <- frees (get rConcs ru, get rActs ru, get rInfo ru)- guard $ not (lvarSort v == LSortPub || v `S.member` boundVars)- return v---- | Report on sort clashes.-unboundReport :: OpenTheory -> WfErrorReport-unboundReport thy = do- RuleItem ru <- get thyItems thy- unboundCheck ("rule " ++ quote (showRuleCaseName ru) ++- " has unbound variables: "- ) ru---- | Report on facts usage.-factReports :: OpenTheory -> WfErrorReport-factReports thy = concat- [ reservedReport, freshFactArguments, specialFactsUsage- , factUsage, inexistentActions- ]- where- ruleFacts ru =- ( "rule " ++ quote (showRuleCaseName ru)- , extFactInfo <$> concatMap (`get` ru) [rPrems, rActs, rConcs])-- -- NOTE: The check that the number of actual function arguments in a term- -- agrees with the arity of the function as given by the signature is- -- enforced by the parser and implicitly checked in 'factArity'.-- theoryFacts = -- sortednubOn (fst &&& (snd . snd)) $- do ruleFacts <$> get thyCache thy- <|> do RuleItem ru <- get thyItems thy- return $ ruleFacts ru- <|> do LemmaItem l <- get thyItems thy- return $ (,) ("lemma " ++ quote (get lName l)) $ do- fa <- formulaFacts (get lFormula l)- return $ (text (show fa), factInfo fa)-- -- we must compute all important information up-front in order to- -- mangle facts with terms with bound variables and such without them- extFactInfo fa = (prettyLNFact fa, factInfo fa)-- factInfo :: Fact t -> (FactTag, Int, Multiplicity)- factInfo fa = (factTag fa, factArity fa, factMultiplicity fa)-- --- Check for usage of protocol facts with reserved names- reservedReport = do- (origin, fas) <- theoryFacts- case mapMaybe reservedFactName fas of- [] -> []- errs -> return $ (,) "reseved names" $ foldr1 ($--$) $- wrappedText ("The " ++ origin ++- " contains facts with reserved names:")- : map (nest 2) errs-- reservedFactName (ppFa, info@(ProtoFact _ name _, _,_))- | map toLower name `elem` ["fr","ku","kd","out","in"] =- return $ ppFa $-$ text ("show:" ++ show info)- reservedFactName _ = Nothing-- freshFactArguments = do- ru <- thyProtoRules thy- fa@(Fact FreshFact [m]) <- get rPrems ru- guard (not (isMsgVar m || isFreshVar m))- return $ (,) "Fr facts must only use a fresh- or a msg-variable" $- text ("rule " ++ quote (showRuleCaseName ru)) <->- text "fact:" <-> prettyLNFact fa-- -- Check for the usage of special facts at wrong positions- specialFactsUsage = do- ru <- thyProtoRules thy- let lhs = [ fa | fa <- get rPrems ru- , factTag fa `elem` [KUFact, KDFact, OutFact] ]- rhs = [ fa | fa <- get rConcs ru- , factTag fa `elem` [FreshFact, KUFact, KDFact, InFact] ]- check _ [] = mzero- check msg fas = return $ (,) "special fact usage" $- text ("rule " ++ quote (showRuleCaseName ru)) <-> text msg $-$- (nest 2 $ fsep $ punctuate comma $ map prettyLNFact fas)-- msum [ check "uses disallowed facts on left-hand-side:" lhs- , check "uses disallowed facts on right-hand-side:" rhs ]-- -- Check for facts with equal name modulo capitalization, but different- -- multiplicity or arity.- factUsage = do- clash <- clashesOn factIdentifier (snd . snd) theoryFacts'- return $ (,) "fact usage" $ numbered' $ do- (origin, (ppFa, info@(tag, _, _))) <- clash- return $ text (origin ++- ", fact " ++ show (map toLower $ factTagName tag) ++- ": " ++ showInfo info)- $-$ nest 2 ppFa- where- showInfo (tag, k, multipl) = show $ (showFactTag tag, k, multipl)- theoryFacts' = [ (ru, fa) | (ru, fas) <- theoryFacts, fa <- fas ]- factIdentifier (_, (_, (tag, _, _))) = map toLower $ factTagName tag--- -- Check that every fact referenced in a formula is present as an action- -- of a protocol rule. We have to add the linear "K/1" fact, as the- -- WF-check cannot rely on a loaded intruder theory.- ruleActions = S.fromList $ map factInfo $- kLogFact undefined- : dedLogFact undefined- : kuFact undefined- : (do RuleItem ru <- get thyItems thy; get rActs ru)-- inexistentActions = do- LemmaItem l <- get thyItems thy- fa <- sortednub $ formulaFacts (get lFormula l)- let info = factInfo fa- name = get lName l- if info `S.member` ruleActions- then []- else return $ (,) "lemma actions" $- text ("lemma " ++ quote name ++ " references action ") $-$- nest 2 (text $ show info) $-$- text "but no rule has such an action."----- | Gather all facts referenced in a formula.-formulaFacts :: Formula s c v -> [Fact (VTerm c (BVar v))]-formulaFacts =- foldFormula atomFacts- (const mempty)- id- (const mappend) (const $ const id)- where- atomFacts (Action _ fa) = [fa]- atomFacts (EqE _ _) = mempty- atomFacts (Less _ _) = mempty- atomFacts (Last _) = mempty---- | Gather all terms referenced in a formula.-formulaTerms :: Formula s c v -> [VTerm c (BVar v)]-formulaTerms =- foldFormula atomTerms (const mempty) id (const mappend) (const $ const id)- where- atomTerms (Action i fa) = i : factTerms fa- atomTerms (EqE t s) = [t, s]- atomTerms (Less i j) = [i, j]- atomTerms (Last i) = [i]---- TODO: Perhaps a lot of errors would be captured when making the signature--- of facts, term, and atom constructors explicit.-lemmaAttributeReport :: OpenTheory -> WfErrorReport-lemmaAttributeReport thy = do- lem <- theoryLemmas thy- guard $ get lTraceQuantifier lem == ExistsTrace- && ReuseLemma `elem` get lAttributes lem- return ( "attributes"- , text "lemma" <-> (text $ quote $ get lName lem) <> colon <->- text "cannot reuse 'exists-trace' lemmas"- )---- | Check for mistakes in lemmas.------ TODO: Perhaps a lot of errors would be captured when making the signature--- of facts, term, and atom constructors explicit.-formulaReports :: OpenTheory -> WfErrorReport-formulaReports thy = do- (header, fm) <- annFormulas- msum [ ((,) "quantifier sorts") <$> checkQuantifiers header fm- , ((,) "formula terms") <$> checkTerms header fm- , ((,) "guardedness") <$> checkGuarded header fm- ]- where- annFormulas = do LemmaItem l <- get thyItems thy- let header = "lemma " ++ quote (get lName l)- fm = get lFormula l- return (header, fm)- <|> do AxiomItem ax <- get thyItems thy- let header = "axiom " ++ quote (get axName ax)- fm = get axFormula ax- return (header, fm)-- -- check that only message and node variables are used- checkQuantifiers header fm- | null disallowed = []- | otherwise = return $ fsep $- (text $ header ++ "uses quantifiers with wrong sort:") :- (punctuate comma $ map (nest 2 . text . show) disallowed)- where- binders = foldFormula (const mempty) (const mempty) id (const mappend)- (\_ binder rest -> binder : rest) fm- disallowed = filter (not . (`elem` [LSortMsg, LSortNode]) . snd) binders-- -- check that only bound variables and public names are used- checkTerms header fm- | null offenders = []- | otherwise = return $- (fsep $- (text $ header ++ " uses terms of the wrong form:") :- (punctuate comma $ map (nest 2 . text . quote . show) offenders)- ) $--$- wrappedText- "The only allowed terms are public names and bound node and message\- \ variables. If you encounter free message variables, then you might\- \ have forgotten a #-prefix. Sort prefixes can only be dropped where\- \ this is unambiguous."- where- offenders = filter (not . allowed) $ formulaTerms fm- allowed (viewTerm -> Lit (Var (Bound _))) = True- allowed (viewTerm -> Lit (Con (Name PubName _))) = True- allowed _ = False-- -- check that the formula can be converted to a guarded formula- checkGuarded header fm = case formulaToGuarded fm of- Left err -> return $- text (header ++ " cannot be converted to a guarded formula:") $-$- nest 2 err- Right _ -> []------- | Check that all rules are multipliation restricted. Compared--- to the definition in the paper we are slightly more lenient.--- We also accept a rule that is an instance of a multiplication--- restricted rule.--- 1. Consistently abstract terms with outermost reducible function symbols--- occuring in lhs with fresh variables in rule.--- 2. check vars(rhs) subset of vars(lhs) u V_Pub for abstracted rule for abstracted variables.--- 3. check that * does not occur in rhs of abstracted rule.-multRestrictedReport :: OpenTheory -> WfErrorReport-multRestrictedReport thy = do- ru <- theoryRules thy- (,) "multiplication restriction of rules" <$>- case restrictedFailures ru of- ([],[]) -> []- (mults, unbounds) ->- return $- (text "The following rule is not multiplication restricted:")- $-$ (nest 2 (prettyProtoRuleE ru))- $-$ (text "")- $-$ (text "After replacing reducible function symbols in lhs with variables:")- $-$ (nest 2 $ prettyProtoRuleE (abstractRule ru))- $-$ (text "")- $-$ (if null mults then mempty- else nest 2 $ (text "Terms with multiplication: ") <-> (prettyLNTermList mults))- $-$ (if null unbounds then mempty- else nest 2 $ (text "Variables that occur only in rhs: ") <-> (prettyVarList unbounds))- where- abstractRule ru@(Rule i lhs acts rhs) =- (`evalFreshAvoiding` ru) . (`evalBindT` noBindings) $ do- Rule i <$> mapM (traverse abstractTerm) lhs- <*> mapM (traverse replaceAbstracted) acts- <*> mapM (traverse replaceAbstracted) rhs-- abstractTerm (viewTerm -> FApp (NonAC o) args) | o `S.member` irreducible =- fAppNonAC o <$> mapM abstractTerm args- abstractTerm (viewTerm -> Lit l) = return $ lit l- abstractTerm t = varTerm <$> importBinding (`LVar` sortOfLNTerm t) t "x"-- replaceAbstracted t = do- b <- lookupBinding t- case b of- Just v -> return $ varTerm v- Nothing ->- case viewTerm t of- FApp o args ->- fApp o <$> mapM replaceAbstracted args- Lit l -> return $ lit l-- restrictedFailures ru = (mults, unbound ruAbstr \\ unbound ru)- where- ruAbstr = abstractRule ru-- mults = [ mt | Fact _ ts <- get rConcs ru, t <- ts, mt <- multTerms t ]-- multTerms t@(viewTerm -> FApp (AC Mult) _) = [t]- multTerms (viewTerm -> FApp _ as) = concatMap multTerms as- multTerms _ = []-- unbound ru = [v | v <- frees (get rConcs ru) \\ frees (get rPrems ru)- , lvarSort v /= LSortPub ]--- irreducible = irreducibleFunctionSymbols $ get (sigpMaudeSig . thySignature) thy------ | All 2-multicombinations of a list.--- multicombine2 :: [a] -> [(a,a)]--- multicombine2 xs0 = do (x,xs) <- zip xs0 $ tails xs0; (,) x <$> xs------------------------------------------------------------------------------------ Theory------------------------------------------------------------------------------------- | Returns a list of errors, if there are any.-checkWellformedness :: OpenTheory- -> WfErrorReport-checkWellformedness thy = concatMap ($ thy)- [ unboundReport- , freshNamesReport- , publicNamesReport- , ruleSortsReport- , factReports- , formulaReports- , lemmaAttributeReport- , multRestrictedReport- ]---- | Adds a note to the end of the theory, if it is not well-formed.-noteWellformedness :: WfErrorReport -> OpenTheory -> OpenTheory-noteWellformedness report thy =- addComment wfErrorReport thy- where- wfErrorReport- | null report = text "All well-formedness checks were successful."- | otherwise = vsep- [ text "WARNING: the following wellformedness checks failed!"- , prettyWfErrorReport report- ]-
src/Web/Dispatch.hs view
@@ -138,18 +138,11 @@ -> AutoProver -> IO TheoryMap loadTheories readyMsg thDir thLoader autoProver = do- mkImageDir thPaths <- filter (".spthy" `isSuffixOf`) <$> getDirectoryContents thDir theories <- catMaybes <$> mapM loadThy (zip [1..] (map (thDir </>) thPaths)) putStrLn readyMsg return $ M.fromList theories where- -- Create image directory- mkImageDir = do- let dir = thDir </> imageDir- existsDir <- doesDirectoryExist dir- unless existsDir (createDirectory dir)- -- Load theories loadThy (idx, path) = E.handle catchEx $ do thy <- thLoader path
src/Web/Hamlet.hs view
@@ -32,7 +32,7 @@ import Data.Ord import Data.Time.Format import Data.Version (showVersion)-import Text.Blaze.Html5 (preEscapedString)+import Text.Blaze.Html (preEscapedToMarkup) import System.Locale @@ -60,6 +60,7 @@ -> Widget -- rootTpl theories form enctype nonce = [whamlet| rootTpl theories = [whamlet|+ $newline never <div class="ui-layout-container"> <div class="ui-layout-north"> <div class="ui-layout-pane">@@ -88,6 +89,7 @@ -- | Template for listing theories. theoriesTpl :: TheoryMap -> Widget theoriesTpl thmap = [whamlet|+ $newline never $if M.null thmap <strong>No theories loaded!</strong> $else@@ -118,6 +120,7 @@ -- | Template for single line in table on root page. theoryTpl :: (TheoryIdx, TheoryInfo) -> Widget theoryTpl th = [whamlet|+ $newline never <tr> <td> <a href=@{OverviewR (fst th) TheoryHelp}>@@ -139,6 +142,7 @@ -- threadsTpl :: (HamletValue h, HamletUrl h ~ WebUIRoute) => [T.Text] -> h {- threadsTpl threads = [whamlet|+ $newline never <h2>Threads <p> This page lists all threads that are currently registered as@@ -161,6 +165,7 @@ -- | Template for header frame (various information) headerTpl :: TheoryInfo -> Widget headerTpl info = [whamlet|+ $newline never <div class="layout-pane-north"> <div #header-info> Running@@ -200,7 +205,9 @@ proofStateTpl :: RenderUrl -> TheoryInfo -> IO Widget proofStateTpl renderUrl ti = do let res = renderHtmlDoc $ theoryIndex renderUrl (tiIndex ti) (tiTheory ti)- return [whamlet| #{preEscapedString res} |]+ return [whamlet|+ $newline never+ #{preEscapedToMarkup res} |] -- | Framing/UI-layout template (based on JavaScript/JQuery) overviewTpl :: RenderUrl@@ -211,6 +218,7 @@ proofState <- proofStateTpl renderUrl info mainView <- pathTpl renderUrl info path return [whamlet|+ $newline never <div .ui-layout-north> ^{headerTpl info} <div .ui-layout-west>@@ -235,11 +243,14 @@ -> TheoryPath -- ^ Path to display on load -> IO Widget pathTpl renderUrl info path =- return $ [whamlet| #{htmlThyPath renderUrl info path} |]+ return $ [whamlet|+ $newline never+ #{htmlThyPath renderUrl info path} |] -- | Template for introduction. introTpl :: Widget introTpl = [whamlet|+ $newline never <div id="logo"> <p> <img src="/static/img/tamarin-logo-3-0-0.png">@@ -278,6 +289,7 @@ -- -> Html -- ^ Nonce field -- -> h formTpl action label form enctype nonce = [whamlet|+ $newline never <form action=@{action} method=POST enctype=#{enctype}> ^{form} <div .submit-form>
src/Web/Handler.hs view
@@ -63,9 +63,11 @@ import Data.String (fromString) import Data.List (intersperse) import Data.Monoid (mconcat)+import Data.Conduit as C ( ($$), runResourceT)+import Data.Conduit.List (consume) import qualified Blaze.ByteString.Builder as B-import qualified Data.ByteString.Lazy.Char8 as BS+import qualified Data.ByteString.Char8 as BS import qualified Data.Map as M import qualified Data.Text as T import Data.Text.Encoding@@ -326,7 +328,7 @@ theories <- getTheories defaultLayout $ do setTitle "Welcome to the Tamarin prover"- addWidget (rootTpl theories)+ rootTpl theories data File = File T.Text deriving Show@@ -337,21 +339,24 @@ case result of Nothing -> setMessage "Post request failed."- Just fileinfo- | BS.null $ fileContent fileinfo -> setMessage "No theory file given."- | otherwise -> do- yesod <- getYesod- closedThy <- liftIO $ parseThy yesod (BS.unpack $ fileContent fileinfo)- case closedThy of- Left err -> setMessage $ "Theory loading failed:\n" <> toHtml err- Right thy -> do- void $ putTheory Nothing- (Just $ Upload $ T.unpack $ fileName fileinfo) thy- setMessage "Loaded new theory!"+ Just fileinfo -> do+ -- content <- liftIO $ LBS.fromChunks <$> (fileSource fileinfo $$ consume)+ content <- liftIO $ runResourceT (fileSource fileinfo C.$$ consume)+ if null content+ then setMessage "No theory file given."+ else do+ yesod <- getYesod+ closedThy <- liftIO $ parseThy yesod (concatMap BS.unpack content)+ case closedThy of+ Left err -> setMessage $ "Theory loading failed:\n" <> toHtml err+ Right thy -> do+ void $ putTheory Nothing+ (Just $ Upload $ T.unpack $ fileName fileinfo) thy+ setMessage "Loaded new theory!" theories <- getTheories defaultLayout $ do setTitle "Welcome to the Tamarin prover"- addWidget (rootTpl theories)+ rootTpl theories -- | Show overview over theory (framed layout).@@ -361,7 +366,7 @@ defaultLayout $ do overview <- liftIO $ overviewTpl renderF ti path setTitle (toHtml $ "Theory: " ++ get thyName (tiTheory ti))- addWidget overview+ overview -- | Show source (pretty-printed open theory). getTheorySourceR :: TheoryIdx -> Handler RepPlain
src/Web/Theory.hs view
@@ -26,6 +26,8 @@ ) where +import Debug.Trace (trace)+ import Data.Char (toUpper) import Data.List import qualified Data.Map as M@@ -44,7 +46,7 @@ import Extension.Data.Label -import Text.Blaze.Html5 (preEscapedString, toHtml)+import Text.Blaze.Html (preEscapedToMarkup, toHtml) import qualified Text.Dot as D import Text.Hamlet (Html, hamlet) import Text.PrettyPrint.Html@@ -391,7 +393,7 @@ pp :: HtmlDoc Doc -> Html pp d = case renderHtmlDoc d of [] -> toHtml "Trying to render document yielded empty string. This is a bug."- cs -> preEscapedString cs+ cs -> preEscapedToMarkup cs go (TheoryMethod _ _ _) = pp $ text "Cannot display theory method." @@ -408,6 +410,7 @@ go (TheoryLemma _) = pp $ text "Implement lemma pretty printing!" go TheoryHelp = [hamlet|+ $newline never <p> Theory: #{get thyName $ tiTheory info} \ (Loaded at #{formatTime defaultTimeLocale "%T" $ tiTime info}@@ -563,7 +566,8 @@ ] if imgGenerated then return imgPath- else return $ imageDir ++ "/img/delete.png"+ else trace ("WARNING: failed to convert:\n '" ++ dotPath ++ "'")+ (return imgPath) dotToImg dotMode dotFile imgFile = do (ecode,_out,err) <- readProcessWithExitCode dotCommand
src/Web/Types.hs view
@@ -4,7 +4,7 @@ Copyright : (c) 2011 Cedric Staub License : GPL-3 -Maintainer : Cedric Staub <cstaub@ethz.ch>+Maintainer : Simon Meier <iridcode@gmail.com> Stability : experimental Portability : non-portable -}@@ -239,16 +239,6 @@ -- Routing ------------------------------------------------------------------------------ --- Quasi-quotation syntax changed from GHC 6 to 7,--- so we need this switch in order to support both.-#if __GLASGOW_HASKELL__ >= 700-#define HAMLET hamlet-#define PARSE_ROUTES parseRoutes-#else-#define HAMLET $hamlet-#define PARSE_ROUTES $parseRoutes-#endif- -- This is a hack we need to work around a bug (?) in the -- C pre-processor. In order to define multi-pieces we need -- the asterisk symbol, but the C pre-processor always chokes@@ -329,7 +319,8 @@ defaultLayout' w = do page <- widgetToPageContent w message <- getMessage- hamletToRepHtml [HAMLET|+ hamletToRepHtml [hamlet|+ $newline never !!! <html> <head>
tamarin-prover.cabal view
@@ -1,7 +1,7 @@ cabal-version: >= 1.8 build-type: Simple name: tamarin-prover-version: 0.8.1.0+version: 0.8.2.0 license: GPL license-file: LICENSE category: Theorem Provers@@ -73,6 +73,7 @@ -- classic security protocols examples/classic/TLS_Handshake.spthy examples/classic/NSLPK3.spthy+ examples/classic/NSPK3.spthy -- loops examples/loops/Minimal_Crypto_API.spthy@@ -80,15 +81,21 @@ examples/loops/Minimal_Create_Use_Destroy.spthy examples/loops/Minimal_Typing_Example.spthy examples/loops/Minimal_Loop_Example.spthy+ examples/loops/Minimal_HashChain.spthy examples/loops/Typing_and_Destructors.spthy examples/loops/JCS12_Typing_Example.spthy examples/loops/TESLA_Scheme1.spthy+ examples/loops/TESLA_Scheme2_lossless.spthy+ examples/loops/TESLA_Scheme2.spthy -- related work examples/related_work/AIF_Moedersheim_CCS10/Keyserver.spthy- examples/related_work/StatVerif_ARR_CSF11/StatVerif_Example1.spthy+ examples/related_work/StatVerif_ARR_CSF11/StatVerif_Security_Device.spthy+ examples/related_work/StatVerif_ARR_CSF11/StatVerif_GM_Contract_Signing.spthy examples/related_work/TPM_DKRS_CSF11/Envelope.spthy- examples/related_work/TPM_DKRS_CSF11/RunningExample.spthy+ examples/related_work/TPM_DKRS_CSF11/TPM_Exclusive_Secrets.spthy+ examples/related_work/YubiSecure_KS_STM12/Yubikey.spthy+ examples/related_work/YubiSecure_KS_STM12/Yubikey_and_YubiHSM.spthy -- CSF'12 case studies examples/csf12/Artificial.spthy@@ -157,7 +164,7 @@ executable tamarin-prover if flag(threaded)- ghc-options: -threaded+ ghc-options: -threaded -eventlog -- Note that eager blackholing lead to segfaults: See GHC Ticket #6146 -- Morevoer, it seems that the bug is not fully fixed on GHC 7.4.2, as we@@ -183,23 +190,20 @@ -- To help the top-down solver we put the more difficult to solve yesod -- dependencies up front. build-depends:- -- not direct dependencies, but wai-extra specifies its dependencies- -- to lax and thus breaks due to the upgrade of fast-logger- fast-logger == 0.0.2- , wai-logger == 0.1.* - , bytestring == 0.9.*- , blaze-html == 0.4.*- , http-types == 0.6.*+ bytestring >= 0.9+ , blaze-html == 0.5.*+ , http-types == 0.7.* , blaze-builder == 0.3.*- , yesod-core == 1.0.*- , yesod-json == 1.0.*- , yesod-static == 1.0.*+ , yesod-core == 1.1.*+ , yesod-json == 1.1.*+ , yesod-static == 1.1.*+ , conduit == 0.5.* -- , yesod-form == 0.4.* -- required once we reactivate editing , text == 0.11.*- , wai == 1.2.*- , hamlet == 1.0.*- , warp == 1.2.*+ , wai == 1.3.*+ , hamlet == 1.1.*+ , warp == 1.3.* , aeson == 0.6.* , old-locale == 1.0.* , monad-control == 0.3.*@@ -230,8 +234,9 @@ , parallel == 3.2.* , HUnit == 1.2.* - , tamarin-prover-utils >= 0.8.1 && < 0.9- , tamarin-prover-term >= 0.8.1 && < 0.9+ , tamarin-prover-utils >= 0.8.2 && < 0.9+ , tamarin-prover-term >= 0.8.2 && < 0.9+ , tamarin-prover-theory >= 0.8.2 && < 0.9 other-modules:@@ -249,42 +254,6 @@ Main.Mode.Intruder Main.Mode.Test - Theory- Theory.Proof-- Theory.Constraint.Solver- Theory.Constraint.Solver.CaseDistinctions- Theory.Constraint.Solver.Contradictions- Theory.Constraint.Solver.Goals- Theory.Constraint.Solver.Reduction- Theory.Constraint.Solver.Simplify- Theory.Constraint.Solver.ProofMethod- Theory.Constraint.Solver.Types- Theory.Constraint.System- Theory.Constraint.System.Dot- Theory.Constraint.System.Guarded- Theory.Constraint.System.Constraints-- Theory.Model- Theory.Model.Atom- Theory.Model.Fact- Theory.Model.Formula- Theory.Model.Rule- Theory.Model.Signature-- Theory.Text.Parser- Theory.Text.Parser.Token- Theory.Text.Parser.UnitTests- Theory.Text.Pretty-- Theory.Tools.AbstractInterpretation- Theory.Tools.IntruderRules- Theory.Tools.LoopBreakers- Theory.Tools.RuleVariants- Theory.Tools.Wellformedness- Theory.Tools.EquationStore- Theory.Tools.InjectiveFactInstances- Web.Dispatch Web.Hamlet Web.Handler@@ -292,6 +261,9 @@ Web.Settings Web.Theory Web.Types++ Test.ParserTests+ source-repository head type: git