packages feed

scyther-proof-0.6.0.0: data/isabelle/src/ESPLogic/espl_definitions.ML

(*****************************************************************************
 * ESPL --- an embedded security protocol logic
 *          http://people.inf.ethz.ch/meiersi/espl/
 *
 *   Copyright (c) 2009-2011, Simon Meier, ETH Zurich, Switzerland
 *
 * Extension to compromising adversaries:
 *
 *   Copyright (c) 2010-2011, Martin Schaub, ETH Zurich, Switzerland
 *
 * All rights reserved. See file LICENCE for more information.
 ******************************************************************************)
(*
Commands for role and protocols definitions in the ESPLogic.

Side effect: registers the two definitional commands "role" and "protocol".
*)

signature ESPL_DEFINITIONS =
sig
  (* Additional Utilities *)
  val unnamed_caseN: string

  datatype steptyp = Recv | Send | Note of term
  val mk_rolestep: steptyp * term * term -> term
  val dest_rolestep: term -> steptyp * term * term
  val dest_role: term -> (steptyp * term * term) list
  val dest_variable_store_lookup: term -> term * (term * term)
  val dest_non_refl_eq: typ -> term -> term * term

  val get_unfold_thms: Proof.context -> term -> thm list
  val get_rolestep_msg: Proof.context -> string -> term

  (* Role Definitions *)
 val define_rolestep_cmd
   : string -> Args.src list -> steptyp * term * term -> local_theory -> 
     ((term * thm) * (term * thm)) * local_theory
 
  val define_role_cmd
   : binding * ((binding * Args.src list) * string) -> local_theory ->
     ((term * thm) * ((term * thm) * (term * thm)) list) * local_theory

  (* Protocol Definitions *)
  val define_proto_cmd
   : binding * ((binding * Args.src list) * string) -> local_theory -> 
     (term * thm) * local_theory

  (* Managing the Chain Rule and Its Expansion with Named Cases *)
  structure TypeElimRulesData : GENERIC_DATA
  val lookup_type_elim_rules: Proof.context -> TypeElimRulesData.T

  val insert_case_names: Proof.context -> thm -> thm
  val source_case_names: thm -> string list

  val refine_knows_cases
     : Proof.context -> term option -> thm -> thm
  val prepare_sources_elim: Proof.context -> thm -> term -> thm

  structure KnowsCasesCache : NAMED_THMS
  val precompute_source_elim_rules
     : thm -> Proof.context -> (string * thm list) list * local_theory

  (* Type Invariant Definitions Combined With Chain Rule Expansion *)
  val type_invariant_cmd
   : (binding * string) * ((binding * Args.src list) * string) ->
     local_theory -> local_theory
end;

structure ESPL_Definitions: ESPL_DEFINITIONS =
struct

open ESPL_Utils;

(******************************************************************************
**  Additional Utilities
******************************************************************************)


datatype steptyp = Recv | Send | Note of term;

fun mk_rolestep (Send, lbl, msg) = @{const Send} $ lbl $ msg
  | mk_rolestep (Recv, lbl, msg) = @{const Recv} $ lbl $ msg
  | mk_rolestep (Note nTy, lbl, msg) = @{const Note} $ lbl $ nTy $ msg

fun dest_rolestep (Const (@{const_name Send},_) $ lbl $ msg)       = (Send,     lbl, msg)
  | dest_rolestep (Const (@{const_name Recv},_) $ lbl $ msg)       = (Recv,     lbl, msg)
  | dest_rolestep (Const (@{const_name Note},_) $ lbl $ nTy $ msg) = (Note nTy, lbl, msg)
  | dest_rolestep t = raise TERM ("dest_rolestep", [t])

val dest_role = map dest_rolestep o HOLogic.dest_list

(* Destruct a symbolic representation of the contents
   of a protocol variable. 
*)
fun dest_variable_store_lookup (t as (s $ lv)) =
      if (Term.fastype_of s = @{typ "store"})
      then (case HOLogic.strip_tuple lv of
             [v,tid] => (s, (v,tid))
           | _       => 
             raise TERM ("dest_variable_store_lookup: wrong arity",[t]))
      else raise TERM ("dest_variable_store_lookup: wrong type",[t])
  | dest_variable_store_lookup t = 
      raise TERM ("dest_variable_store_lookup",[t]);


(* Destruct an non-reflexive equality *)
fun dest_non_refl_eq ty t = 
  case HOLogic.dest_eq t of
    eq as (lhs,rhs) => 
      if   lhs = rhs
      then raise TERM ("dest_non_refl_eq: reflexive",[t])
      else 
        if   Term.fastype_of lhs = ty
        then eq
        else raise TYPE ("dest_non_refl_eq:",[ty],[t]);


(* Retrieves the unfold theorems for a constent that were previously stored as
   const_name.unfold. This is not very stable but works for our defintions.

   TODO: Check if there is a more stable machinery for that purpose.
*)
fun get_unfold_thms ctxt (Const (c,_)) = Proof_Context.get_thms ctxt (c ^ ".unfold")
  | get_unfold_thms ctxt (Free  (c,_)) = Proof_Context.get_thms ctxt (c ^ ".unfold")
  | get_unfold_thms _ t = raise TERM ("get_unfold_thms", [t])

(* Lookup the term of the message of a role step *)
fun get_rolestep_msg ctxt step_name = 
  Proof_Context.get_thm ctxt (Thm.def_name (step_name ^ "_pt"))
  |> Thm.prop_of
  |> dest_HOL_def
  |> snd

(* Extract identifiers of message variables in a pattern *)
fun pat_msgvars (Const (@{const_name "PEnc"},_) $ m $ k)  = 
      Ord_List.union fast_string_ord (pat_msgvars m) (pat_msgvars k)
  | pat_msgvars (Const (@{const_name "PTup"},_) $ x $ y)  = 
      Ord_List.union fast_string_ord (pat_msgvars x) (pat_msgvars y)
  | pat_msgvars (Const (@{const_name "PHash"},_) $ x)     = 
      pat_msgvars x
  | pat_msgvars (Const (@{const_name "PSymK"},_) $ a $ b) = 
      Ord_List.union fast_string_ord (pat_msgvars a) (pat_msgvars b)
  | pat_msgvars (Const (@{const_name "PAsymPK"},_) $ a)   =
      pat_msgvars a
  | pat_msgvars (Const (@{const_name "PAsymSK"},_) $ a)   =
      pat_msgvars a
  | pat_msgvars (Const (@{const_name "PFresh"},_) $ _)    = 
      []
  | pat_msgvars (Const (@{const_name "PConst"},_) $ _)    = 
      []
  | pat_msgvars (Const (@{const_name "PVar"},_) $ v)      = 
      (case v of
        (Const (@{const_name AVar},_) $ _) => []
      | (Const (@{const_name MVar},_) $ i) => [HOLogic.dest_string i]
      | _ => []
      )
  | pat_msgvars _ = []


(******************************************************************************
**  Role Definitions
******************************************************************************)

(*
  The "role" definition command defines constants for every message and step of
  the role specification and the role specification itself. Furthermore
  wellformedness of the role and several expansion theorems are proven
  automatically. The theorems "role.unfold" can be used if all these
  intermediate definitions need to be simplified away.
*)

(* proves and notes all the theorems associated to a rolestep *)
fun setup_rolestep mk_binding ann_defs lthy =
  let
    val ((msg_const,msg_def),(step_const,step_def)) = ann_defs;

    val ss_step = Simplifier.simpset_of lthy addsimps [step_def];
    val ss_full = ss_step addsimps [msg_def];

    val sendStep_t = @{const sendStep} $ step_const;
    val noteStep_t = @{const noteStep} $ step_const;
    val stepPat_t = @{const stepPat} $ step_const;
    val noteType_t = @{const noteType} $ step_const;

    val inst_t = @{term "inst s i"} $ msg_const;
    val FV_t = @{term "FV"} $ msg_const;
    val FMV_t = @{term "FMV"} $ msg_const;
    val FAV_t = @{term "FAV"} $ msg_const;
  in
   lthy
   (* unfold theorems *)
     |> #2 o Local_Theory.note (mk_binding "unfold", [msg_def,step_def])
   (* step expansions *)
     |> #2 o notes_expansion ss_step
       [ (mk_binding "sendStep_conv", [([sendStep_t],[att_iff_add])]) 
       , (mk_binding "noteStep_conv", [([noteStep_t],[att_iff_add])]) 
       , (mk_binding "stepPat_conv",  [([stepPat_t],[att_simp_add])]) 
       , (mk_binding "noteType_conv",  [([noteType_t],[att_simp_add])])
       ]
   (* full expansions *)
     |> notes_expansion ss_full
       [ (mk_binding "inst_conv", [([inst_t], [att_simp_add])])
       , (mk_binding "FV_conv",   [([FV_t],   [att_simp_add])])
       , (mk_binding "FMV_conv",  [([FMV_t],  [att_simp_add])])
       , (mk_binding "FAV_conv",  [([FAV_t],  [att_simp_add])])
       ]
   (* restore original annotated definitions *)
     |>> K ann_defs
  end

(* defines the constants needed for a rolestep and setups the associated
   theorems using setup_rolestep
*)
fun define_rolestep_cmd prefix srcs (stepty, lbl, msg) lthy =
  let
    val lbl_str = HOLogic.dest_string lbl;

    val step_name = prefix ^ "_" ^ lbl_str;
    val step_const_bind = Binding.name step_name;
    val step_def_bind = (Binding.suffix_name "_def" step_const_bind, srcs);

    val msg_name = step_name ^ "_pt";
    val msg_const_bind = Binding.name msg_name;
    val msg_def_bind = (Binding.suffix_name "_def" msg_const_bind, srcs);
    val msg_def_eq   = Logic.mk_equals (Free (msg_name, @{typ pattern}), msg);
  in
    lthy
     |> Specification.definition 
         ( SOME (msg_const_bind, SOME @{typ pattern}, NoSyn)
         , (msg_def_bind, msg_def_eq) )
     |-> (fn (msg_const, (_, msg_def)) =>
           Specification.definition
             ( SOME (step_const_bind, SOME @{typ rolestep}, NoSyn)
             , ( step_def_bind
               , Logic.mk_equals (Free (step_name, @{typ rolestep}), 
                                  mk_rolestep (stepty, lbl, msg_const))
               ) 
             )
           #>> (fn (step_const, (_, step_def)) =>
                 ((msg_const, msg_def), (step_const, step_def))
               )
         )
     |-> setup_rolestep (fn n => (Binding.qualify true step_name (Binding.name n), srcs))
  end

(* proves and notes all theorems associated to a role *)
fun setup_role mk_binding (defs as ((role_const, role_def), steps)) ctxt =
  let
    val in_set_t = HOLogic.mk_mem (@{term "step :: rolestep"}, 
      @{term "set :: role => rolestep set"} $ role_const);
    val zip_t = 
      @{term "zip :: role => 'a list => (rolestep * 'a) list"}
      $ role_const $ @{term "xs :: 'a list"}
    val splits_t =
      @{term "splits :: role => (role * role) list"} $ role_const

    val pat_defs = map (snd o fst) steps;
    val step_defs = map (snd o snd) steps;
    val step_consts = map (fst o snd) steps;

    val ss_role = Simplifier.simpset_of ctxt addsimps 
                    [role_def, @{thm prefixClose_def}];
    val ss_full = ss_role addsimps (pat_defs @ step_defs @
      @{thms wf_role_def distinct_list_def wf_role_axioms_def});

    val simp_tactic = K (FIRSTGOAL (Simplifier.full_simp_tac ss_full));

    val distinct_steps_thms = prove_distinctness ctxt simp_tactic step_consts;

    val wf_goal = HOLogic.mk_Trueprop (@{const wf_role} $ role_const);

    val avars_t = @{const aVars}$ role_const;

    val lastComStep_t = @{const lastComStep} $ role_const;
    val firstComStep_t = @{const firstComStep} $ role_const;

    val ss_prefixClose = ss_role addsimps distinct_steps_thms;

    fun mk_prefixClose_thm step_def =
      let
        val step = step_def |> Thm.prop_of |> dest_HOL_def |> #1;
        val t =  @{const prefixClose} $ (Free ("s", @{typ store})) $
         (Free ("t", @{typ explicit_trace})) $
         role_const $ step $ (Free ("i", @{typ tid}));
      in expand_term ss_prefixClose t end

    fun pat_def_msgvars pat_def =
      pat_def |> Thm.prop_of |> dest_HOL_def |> #2 |> pat_msgvars;

    val msgvars = Library.foldl 
      (fn (vs,def) => Ord_List.union fast_string_ord vs (pat_def_msgvars def)) 
      ([],pat_defs);

  in
    ctxt
    (* unfold theorems *)
      |> #2 o Local_Theory.note (mk_binding "unfold", role_def :: step_defs @ pat_defs)
    (* wellformed role *)  
      |> #2 o notes_proven simp_tactic
        [ (mk_binding "wf_role",    [([wf_goal],[att_iff_add])]) ]
    (* distinct steps and prefixClose expansions *)
      |> #2 o Local_Theory.notes
        [ (mk_binding "distinct_steps",    [(distinct_steps_thms,[att_iff_add])]) 
        , (mk_binding "prefixClose_convs", 
            [(map mk_prefixClose_thm step_defs, [att_iff_add])])
        ]
    (* expansions *)  
      |> notes_expansion ss_role
        [ (mk_binding "in_set_conv", [([in_set_t],[att_iff_add])])
        , (mk_binding "zip_conv",    [([zip_t],[att_simp_add])])
        , (mk_binding "splits_conv", [([splits_t],[att_simp_add])])
        , (mk_binding "avars_conv", [([avars_t],[att_simp_add])])
        , (mk_binding "lastComStep_conv", [([lastComStep_t],[att_simp_add])])
        , (mk_binding "firstComStep_conv", [([firstComStep_t],[att_simp_add])])
        ]
    (* restore definitions *)
      |>> K defs
  end

(* defines a role and setups all associated theorems using setup_role *)
fun define_role_cmd (raw_bind, raw_spec as ((_,srcs),_)) params_ctxt =
  let
    val (([((_,role_ty),_)],[(role_def_bind, role_eq)]), _) =
      Specification.read_free_spec [(raw_bind, SOME "Protocol.role", NoSyn)] 
        [raw_spec] params_ctxt;
    val steps = role_eq |> dest_HOL_def |> #2 |> dest_role;

    fun mk_definition ann_step_defs =
      let
        val role_eq = Logic.mk_equals (role_eq |> dest_HOL_def |> #1,
          HOLogic.mk_list @{typ rolestep} (map (fst o snd) ann_step_defs));
      in
        Specification.definition 
          ( SOME (raw_bind, SOME role_ty, NoSyn)
          , (role_def_bind, role_eq) )
        #>> (fn (role_const,(_,role_def)) => ((role_const,role_def),ann_step_defs))
      end

    val prefix = Binding.name_of raw_bind;
    fun mk_rel_binding name = (Binding.qualify true prefix (Binding.name name), srcs);
  in
    params_ctxt
      |> fold_map (define_rolestep_cmd prefix srcs) steps
      |-> mk_definition
      |-> setup_role mk_rel_binding
  end

(* registering the role definition command as an outer keyword *)
val roledef_parser = 
  Parse.binding --| Parse.where_ -- (Parse_Spec.opt_thm_name ":" -- Parse.prop);

val _ =
  Outer_Syntax.local_theory @{command_spec "role"} "security protocol role definition" 
    (roledef_parser >> (fn spec => define_role_cmd spec #> snd));

(******************************************************************************
**  Protocol Definitions
******************************************************************************)

(* proves and notes all theorems associated to a protocol definition *)
fun setup_proto mk_binding (ann_def as (proto_const, proto_def)) ctxt = 
  let
    val role_consts = proto_def |> Thm.prop_of 
      |> dest_HOL_def |> #2 |> HOLogic.dest_set;
    val role_unfolds = flat (map (get_unfold_thms ctxt) role_consts);

    val proto_ss = Simplifier.simpset_of ctxt addsimps 
      (proto_def :: @{thms wf_proto_def});
    val full_ss = proto_ss addsimps role_unfolds;

    fun simp_tactic ss = K (FIRSTGOAL (Simplifier.full_simp_tac ss));

    val wf_proto_thm = prove_prop ctxt (simp_tactic proto_ss)
      (HOLogic.mk_Trueprop (@{const wf_proto} $ proto_const));

    val distinct_roles_thms = 
      prove_distinctness ctxt (simp_tactic full_ss) role_consts;

    val in_set_t = HOLogic.mk_mem (@{term "R :: role"}, proto_const);

  in 
    ctxt 
    (* unfolds, distinct roles, and wf_proto *)
      |> #2 o Local_Theory.notes
        [ (mk_binding "unfold",         [(proto_def :: role_unfolds, [])]) 
        , (mk_binding "distinct_roles", [(distinct_roles_thms,[att_iff_add])]) 
        , (mk_binding "wf_proto",       [([wf_proto_thm], [att_iff_add])])
        ]
    (* expansions *)  
      |> #2 o notes_expansion proto_ss
        [ (mk_binding "in_set_conv",    [([in_set_t],[att_iff_add])])
        ]
    (* restore annotated definition *)
      |> pair ann_def
  end;

fun define_proto_cmd (raw_bind, raw_spec as ((_,srcs),_)) params_ctxt =
  let
    val ((proto_const, (_, proto_def)), spec_ctxt) = params_ctxt 
      |> Specification.definition_cmd 
        (SOME (raw_bind, SOME "Protocol.proto", NoSyn), raw_spec) true;

    val proto_name          = Binding.name_of raw_bind;
    fun mk_rel_binding name = 
      (Binding.qualify true proto_name (Binding.name name), srcs);
  in
    setup_proto mk_rel_binding (proto_const, proto_def) spec_ctxt
    |> apsnd (Local_Theory.raw_theory (fn thy =>
        let
          val locale_b = Binding.name (proto_name ^ "_state");
          val _ = Output.writeln ("\nlocale\n  " ^ (Binding.name_of locale_b));
          
          (* HACK as I didn't know how to get a hand of the right constant *)
          val proto_t = Syntax.read_term_global thy proto_name;
          val (_,lthy) = 
            add_simple_locale locale_b "reachable_state" [SOME proto_t] thy;
        in
          lthy |> Local_Theory.exit_global
        end))
  end;

(* registering the protocol definition command *)
val protodef_parser = 
  Parse.binding --| Parse.where_ -- (Parse_Spec.opt_thm_name ":" -- Parse.prop);

val _ =
  Outer_Syntax.local_theory @{command_spec "protocol"} "security protocol definition" 
    (protodef_parser >> (fn spec => define_proto_cmd spec #> #2));


(******************************************************************************
**  Sources Case Name Computation
******************************************************************************)


val unnamed_caseN = "unnamed"

(* Given a list of premises, it extracts the content of the first premise being
   a "case_name" hint.
*)
fun extract_case_name prems = 
  case Hints.gather_by_name "case_name" prems of
    (name :: _) => HOLogic.dest_string name
  | [] => raise TERM ("extract_case_name", prems);


(* Given a list of case premises, it extracts the rolestep constant name
   whose specification message was the start for this case.
*)
fun extract_rolestep_name prems = 
  case Hints.gather_by_name "decrChainFrom" prems of
    (data :: _) => 
      (case HOLogic.strip_tuple data of
        (_ :: _ :: Const (step_name,_) :: _) => step_name
      | _ => raise TERM ("extract_rolestep_name", prems)
      )
  | _ => raise TERM ("extract_rolestep_name", prems)


(* Name the variable constructors *)
fun var_name (Const (@{const_name "AVar"},_) $ i) = HOLogic.dest_string i
  | var_name (Const (@{const_name "MVar"},_) $ i) = HOLogic.dest_string i
  | var_name _                                    = "var"

(* Name the execution literal constructors *)
fun msg_lit_name (Const (@{const_name "ENonce"},_) $ n $ _) = HOLogic.dest_string n
  | msg_lit_name (Const (@{const_name "EConst"},_) $ c)     = HOLogic.dest_string c
  | msg_lit_name _                                          = "lit";


(* Name the pattern constructors *)
fun msg_name (Const (@{const_name "Enc"},_) $ _ $ _)  = "enc"
  | msg_name (Const (@{const_name "Tup"},_) $ _ $ _)  = "tup"
  | msg_name (Const (@{const_name "Hash"},_) $ _)     = "hash"
  | msg_name (Const (@{const_name "K"},_) $ _ $ _)    = "K"
  | msg_name (Const (@{const_name "PK"},_) $ _)       = "PK"
  | msg_name (Const (@{const_name "SK"},_) $ _)       = "SK"
  | msg_name (Const (@{const_name "Lit"},_) $ l)      = msg_lit_name l
  | msg_name _                                        = "msg"


(* extract all non-reflexive equalities,
   if there is a lhs with a substitution then use this name
   else use the name of the first rhs
*)
fun extract_msg_name prems =
  let
    val dest_msg_eq = dest_non_refl_eq @{typ execmsg};
    val subst_var = var_name o 
                    #1 o #2 o dest_variable_store_lookup o 
                    #1 o dest_msg_eq;
    val fixed_msg = msg_name o #2 o dest_msg_eq;

    val subst = try hd (ESPL_Utils.gather_props subst_var prems);
    val fixed = try hd (ESPL_Utils.gather_props fixed_msg prems);
  in
    if is_none subst andalso is_none fixed then 
      NONE
    else
      if subst = fixed then 
        SOME (the_default "" subst)
      else
        SOME (
          (the_default "" subst) ^
          (if is_some subst andalso is_some fixed then "_" else "") ^
          (the_default "" fixed)
        )
  end  

(* get the case name from a term representing an implication *)
fun get_case_name t =
  let
    val prems = Logic.strip_imp_prems (Term.strip_all_body t);
  in
    case try extract_case_name prems of
      SOME "decrypt" =>
        (let
          val step_name = extract_rolestep_name prems;          
          val opt_name  = extract_msg_name prems;
        in
          (List.last (space_explode "." step_name)) ^
          (case opt_name of
            SOME m => "_" ^  m
          | NONE   => "")
        end
        handle _ => "decrypt")
    | SOME name => name
    | NONE => unnamed_caseN
  end;

(* Compute the case names for the given theorem being a case distinction in
   eleminitation form gained from spezializing the knows_cases theorem. 
*)
fun source_case_names th =
  th |> Thm.prop_of
     |> Logic.strip_imp_prems
     |> map get_case_name
     |> blank_zero_numbers (op =)
     |> map (append_optional_number "_")


fun mk_case_name_hint thy name =
  Hints.mk_hint_thm thy "case_name" (HOLogic.mk_string name);

(* Insert the case names into a theorem *)
fun insert_case_names ctxt th =
  let
    val thy           = Proof_Context.theory_of ctxt;
    val cases         = source_case_names th;
    val case_hint     = Hints.mk_hint_thm thy "case_name" o HOLogic.mk_string o nth cases;
    fun ins_tac (_,i) = Method.insert_tac [case_hint (i-1)] i;
  in
    HOL_Ext.refine_rule ctxt 
      (K (ALLGOALS (Hints.remove_all_hints_tac THEN' SUBGOAL ins_tac))) th
  end


(******************************************************************************
**  Chain Rule Expansion
******************************************************************************)

(* FIXME: Not sure if there isn't some additional setup needed.
          For NamedThms it is..
*)

(* Storing the rules used for eliminating the type information *)
structure TypeElimRulesData = Generic_Data
(
  type T = Thm.thm list;
  val empty = [];
  val extend = I;
  fun merge (ths1, ths2) = distinct Thm.eq_thm (ths1 @ ths2)
);

val lookup_type_elim_rules = TypeElimRulesData.get o Context.Proof;


(* Reorder the bound variables introduced by the expansion of a ticket.

   The argument is premise of the form !! x1..xn. A1 ==> ... An ==> B
   The result is a conversion justifying the permutation of x1...xn
   according to their left-to-right occurrence in the first equality 
   of the form

     s(MV v tid) = msg
*)
fun reorder_bound_conv ctxt ct = 
  let
    val t = Thm.term_of ct;
    val dest_subst_eq =
      apfst (dest_variable_store_lookup) o dest_non_refl_eq @{typ execmsg};
  in
    case 
      t |> Term.strip_all_body 
        |> Logic.strip_imp_prems
        |> ESPL_Utils.gather_props dest_subst_eq
        |> try hd
    of
      SOME ((_, (_, tidt)), rhst) => 
        let
          val n = length (Term.strip_all_vars t);
          val p = [] |> fold HOL_Ext.add_bound_list [tidt, rhst] 
                     |> distinct (op =)
                     |> map (fn i => n - i - 1);
        in
          HOL_Ext.forall_permute_conv ctxt p ct
        end
    | NONE    => Conv.all_conv ct
  end;


(* reorders the bound variables of the premises according to
   reorder_bound_conv
*)
fun reorder_bound_vars ctxt th =
  HOL_Ext.lift_ground_thm_mod ctxt
    (Conv.fconv_rule o 
     Conv.prems_conv (Thm.nprems_of th) o reorder_bound_conv)
    th;

(* Eliminates the remaining decryption chains starting from symbolically
   instantiated protocol variables using their type assertions.
*)
fun type_elim_tac ctxt ty_elim_ths = 
  let
    fun mk_elim_tac ty_elim_th =
      EVERY'
        [ etac ty_elim_th
        , assume_tac
        , TRY o full_simp_tac (simpset_of ctxt)
        , TRY o (REPEAT o etac @{thm conjE})
        , TRY o Orders.order_tac ctxt []
        ]
  in
    (FIRST' (map mk_elim_tac ty_elim_ths)) THEN'
    (TRY o Clasimp.clarsimp_tac ctxt)
  end;


fun expand_knows_cases_tac ctxt ty_elim_ths =
  REPEAT_DETERM1 (
    FIRST [ safe_tac ctxt
          , CHANGED_PROP (TRYALL (full_simp_tac (simpset_of ctxt)))
          , CHANGED_PROP (TRYALL (type_elim_tac ctxt ty_elim_ths))
          ] 
  );

(* Convert a knows_cases theorem to its elimination form with
   all premises named with the corresponding case name.

   Optionally, changes to the given message term are traced.
*)
fun refine_knows_cases ctxt opt_msg_t case_th =
  let  
    (* don't track changes to first subgoal "m : knows t" *)
    fun mk_track_tac th (_,i) = Method.insert_tac [th] i;

    val ((track_tac, untrack), ctxt') = 
      case opt_msg_t of
        SOME msg_t => apfst (apfst mk_track_tac) 
                        (HOL_Ext.track_HOL_term ctxt msg_t)
      | NONE       => ((K all_tac, I), ctxt);

    val ty_elim_ths = lookup_type_elim_rules ctxt';

    fun tac  lthy   = 
      (ALLGOALS (SUBGOAL track_tac) THEN 
       expand_knows_cases_tac lthy ty_elim_ths);
  in
    HOL_Ext.refine_rule ctxt' tac case_th
    |> untrack
    |> insert_case_names ctxt
    |> reorder_bound_vars ctxt
  end;


(* A more convenient interface to refine_knows_cases

   FIXME: Why can't it be merged with refine_knows_cases?
*)
fun prepare_sources_elim ctxt case_th msg_t =
  let
    val lthy = Variable.auto_fixes msg_t ctxt;

    val knows_ct = 
      HOLogic.mk_mem (msg_t, @{term "knows t"})
      |> HOLogic.mk_Trueprop
      |> Thm.cterm_of (Proof_Context.theory_of lthy);

    val knows_th = Thm.assume knows_ct;
  in 
    (knows_th RS case_th)
    |> HOL_Ext.make_HOL_elim
    |> refine_knows_cases lthy (SOME msg_t)
    |> Thm.implies_intr knows_ct
    |> singleton (Proof_Context.export lthy ctxt) 
    |> Drule.zero_var_indexes
  end;


(*** Precomputing and storing chain rule expansion in the actual theory ***)

structure KnowsCasesCache = Named_Thms
  (val name = @{binding "knows_cases_cache"}
   val description = "Cache of protocol and message specific specializations of the knows_cases theorem")

(* shorthand for attributes modifying the knowledge cases *)
val att_knows_cases_cache_add = Attrib.internal (K KnowsCasesCache.add);
val att_knows_cases_cache_del = Attrib.internal (K KnowsCasesCache.del);

(* Precompute and register the standard chain rule expansions 
*)
fun precompute_source_elim_rules case_th ctxt =
  let
    val _ = Output.writeln "\n  precomputed sources for:"

    fun precompute (msg_t, desc) = 
      let
        val b = Binding.name ("knows_cases_" ^ desc)
        val _ = Output.writeln ("    " ^ Binding.name_of b);
      in
        (b, prepare_sources_elim ctxt case_th msg_t)
      end;

    val prec_ths = Par_List.map precompute
      [ (@{term "LN n i   :: execmsg"}, "Nonce")
      , (@{term "Hash m   :: execmsg"}, "Hash")
      , (@{term "Enc m k  :: execmsg"}, "Enc")
      , (@{term "SK a     :: execmsg"}, "SK")
      , (@{term "K a b    :: execmsg"}, "K")
      , (@{term "KShr A   :: execmsg"}, "KShr")
      ] 

    fun prep_note (b,th) = ((b,[]),[([th],[att_knows_cases_cache_add])]);
  in
     ctxt
(*     |> Local_Theory.target (add_sources_rules (map snd prec_ths)) *)
     |> Local_Theory.notes (map prep_note prec_ths)
  end;


(******************************************************************************
**  Type Invariant Definitions
******************************************************************************)

(* Destruct a mk_typing constructor *)
fun dest_mk_typing (Const (@{const_name mk_typing},_) $ typ) = 
      typ |> HOLogic.dest_list |> map HOLogic.dest_prod
  | dest_mk_typing t = raise TERM ("dest_mk_typing", [t]);

(* proves and notes all the theorems associated to a typing *)
fun setup_typing mk_binding ann_def ctxt =
  let
    val (typ_const,typ_def) = ann_def;

    val autom_tac = auto_tac (map_simpset (fn ss => ss addsimps [typ_def]) ctxt)
    val ss_full = Simplifier.simpset_of ctxt 
               addsimps [typ_def] addsimps @{thms mk_typing_def};
    
    val vars = typ_def |> Thm.prop_of 
      |> ESPL_Utils.dest_HOL_def |> snd |> dest_mk_typing |> map fst;

    val typ_calls = map (curry (op $) typ_const) vars;

    val mono_typ_th = (@{const monoTyp} $ typ_const)
      |> HOLogic.mk_Trueprop
      |> Thm.cterm_of (Proof_Context.theory_of ctxt)
      |> (fn ct => Goal.prove_internal [] ct
           (K autom_tac)
         );
  in
   ctxt
   (* expansions for 'calling' the typing with defined variable *)
     |> #2 o ESPL_Utils.notes_expansion ss_full
        [ (mk_binding "defined_vars", [(typ_calls, [att_iff_add])]) ]
   (* monotonicity theorem *)
     |> Local_Theory.note (mk_binding "monoTyp", [mono_typ_th])
   (* restore original annotated definition *)
     |>> K ann_def 
  end;

(* Defines and setups a typing defined using 'mk_typing'. *)
fun define_typing_cmd ((b,proto_name), ((att,srcs),raw_def)) params_ctxt =
  let
    fun mk_rel_binding name = 
      (Binding.qualify true (Binding.name_of b) (Binding.name name), srcs);

    val ((typ_const, (_, typ_def)), spec_ctxt) = params_ctxt 
      |> Specification.definition_cmd
          (SOME (b, SOME "WeakTyping.typing", NoSyn)
          , ((att,srcs), raw_def)) true;
  in
    setup_typing mk_rel_binding (typ_const, typ_def) spec_ctxt
  end;

(* Add the locale corresponding to the type invariant being defined 
*)
fun add_type_invariant_locale (b,(typ_t, proto_t)) =
  add_simple_locale b "typed_state" 
    [SOME proto_t, NONE, NONE, NONE, SOME typ_t];

(* Defines and setups a typing as well as its corresponding 
   chain rule expansions.
*)
fun type_invariant_cmd (spec as ((typing_b,proto_name),_)) lthy =
  lthy
  |> #2 o define_typing_cmd spec
  |> Local_Theory.raw_theory (fn thy =>
      let
        val typ_name = Binding.name_of typing_b;
        val typ_t    = Syntax.read_term_global thy typ_name;
        val proto_t  = Syntax.read_term_global thy proto_name;
        val locale_b = Binding.name (typ_name ^ "_state");
        val _ = Output.writeln ("\nlocale\n  " ^ (Binding.name_of locale_b));

        val (_,lthy) = 
          add_type_invariant_locale (locale_b, (typ_t, proto_t)) thy;
        val case_th  = Proof_Context.get_thm lthy "knows_cases";
      in
        lthy
        |> #2 o precompute_source_elim_rules case_th
        |> Local_Theory.exit_global 
      end
   );

(* register the type invariant definition command *)
local structure P = Parse and K = Keyword in
  val for_proto = P.$$$ "for" |-- P.term;

  val type_invariant_parser = 
    P.binding -- for_proto --| P.where_ -- (Parse_Spec.opt_thm_name ":" -- P.prop);
  val _ =
    Outer_Syntax.local_theory @{command_spec "type_invariant"} "type invariant definition" 
      (type_invariant_parser >> type_invariant_cmd);
end;

end;