(**************************************************************** Michel St-Martin and Amy Felty December 2015, Coq version V8.4 A Verified Algorithm for Detecting Conflicts in XACML Access Control Rules, Michel St-Martin and Amy P. Felty, In Proceedings of the Fifth ACM SIGPLAN Conference on Certified Programs and Proofs (CPP), January 2016. ***************************************************************) Require Export Core. Require Export Recdef. Require Import Coq.Program.Subset. Notation "~t A" := (trileanNot A) (at level 75, right associativity). Notation "A /\t B" := (trileanAnd A B) (at level 80, right associativity). Notation "A =b B" := (Zeq_bool A B) (at level 90). Inductive srac : Set := | single : core -> nat -> srac | and : srac -> srac -> srac | or : srac -> srac -> srac | not : srac -> srac. Open Scope nat_scope. Definition notCheck (c : core) (n:nat) : srac := if invalidArgs c then single na n else match c with | any r => single (empty r) n | empty r => single (any r) n | timeInRange m M => if m =b 0 then if M =b Z'MIDNIGHT then single (empty (timeReq Z'NM0)) n else single (timeInRange M Z'MIDNIGHT) n else if M =b Z'MIDNIGHT then single (timeInRange Z'0 m) n else or (single (timeInRange Z'0 m) n) (single (timeInRange M Z'MIDNIGHT) n) | intInRange m M => or (single (intLt m) n) (single (intGt M) n) | intGt m => single (intLt (m+1)) n | intLt M => single (intGt (M-1)) n | na => single na n end. Definition trileanOr (t1 t2 : trilean) : trilean := match t1 with | T => match t2 with | NA => NA | _ => T end | F => t2 | NA => NA end. Notation "A \/t B" := (trileanOr A B) (at level 85, right associativity). Fixpoint sracMatch (reql : list reqValue) (s : srac) :trilean := match s with | single c n => coreMatch (nth n reql blank) c | and s1 s2 => sracMatch reql s1 /\t sracMatch reql s2 | or s1 s2 => sracMatch reql s1 \/t sracMatch reql s2 | not s1 => ~t sracMatch reql s1 end. Open Scope Z_scope. Lemma notsCancel: forall (t:trilean), (~t (~t t)) = t. Proof. intros. destruct t; simpl in *; trivial. Qed. Lemma notCheckSoundComp : forall (ls : list reqValue) (c: core) (n:nat), sracMatch ls (notCheck c n) = ~t sracMatch ls (single c n). Proof. intros. unfold notCheck. findIf; unfold coreMatch. rewrite C. simpl. trivial. remember c. induction c0; simpl; remember (nth n ls blank); induction y; try solve[try induction r; trivial]; repeat findIf; try rewrite <- Heqy; simpl; trivial; unfold coreMatch; simpl in *; try (b2PH; unfold MIDNIGHT in *; omega); repeat findIf; b2PH; try omega; try solve[assert (0 <= z <= MIDNIGHT); try solve[destruct z; trivial]; assert (0 <= z0 <= MIDNIGHT); try solve[destruct z0; trivial]; assert (0 <= z1 < MIDNIGHT); try solve[destruct z1; trivial]; b2PH; omega]. Qed. Open Scope nat_scope. Fixpoint depth (s: srac) : nat := match s with | single c n => 1 | or s1 s2 => (depth s1) + (depth s2) + 1 | and s1 s2 => (depth s1) + (depth s2) + 1 | not s1 => (depth s1) + 1 end. Fixpoint notSize (s: srac) : nat := match s with | single c n => 1 | or s1 s2 => (notSize s1) + (notSize s2) + 1 | and s1 s2 => (notSize s1) + (notSize s2) + 1 | not s1 => (notSize s1) + (depth s1) end. Function removeNots (s:srac) {measure notSize s} : srac := match s with | not (single c1 n) => notCheck c1 n | not (or s1 s2) => and (removeNots (not s1)) (removeNots (not s2)) | not (and s1 s2) => or (removeNots (not s1)) (removeNots (not s2)) | not (not s1) => removeNots s1 | and s1 s2 => and (removeNots s1) (removeNots s2) | or s1 s2 => or (removeNots s1) (removeNots s2) | single c n => s end. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. Qed. Theorem strongind_aux : forall P : nat -> Prop, P 0 -> (forall n, (forall m, m <= n -> P m) -> P (S n)) -> forall n, (forall m, ((m <= n) -> P m)). Proof. induction n as [ | n' IHn' ]. intros m H1. inversion H1. assumption. intros. assert (m <= n' \/ m = S n'). omega. inversion H2. apply IHn'; assumption. rewrite H3. apply (H0 n'); assumption. Qed. Lemma weaken : forall P : nat -> Prop, (forall n, (forall m, ((m <= n) -> P m))) -> (forall n, P n). Proof. intros P H n. apply (H n n). apply le_n. Qed. Theorem strongind : forall P : nat -> Prop, P 0 -> (forall n, (forall m, m <= n -> P m) -> P (S n)) -> forall n, P n. Proof. intros. apply weaken. apply strongind_aux; assumption. Qed. Fixpoint numNots (s:srac) : nat := match s with | and s1 s2 => numNots s1 + numNots s2 | or s1 s2 => numNots s1 + numNots s2 | single c n => 0 | not s1 => 1 + numNots s1 end. Ltac unfoldRemoveNotsG := match goal with | [ |- context[removeNots ?s] ] => generalize (removeNots_equation s); intro C; rewrite C in *; clear C end. Ltac unfoldRemoveNots H := match type of H with | context[removeNots ?s] => generalize (removeNots_equation s); intro C; rewrite C in *; clear C end. Definition P (n: nat) : Prop := forall s:srac, depth s = n -> numNots(removeNots s) = 0. Lemma notBase : P 0. Proof. unfold P. intros. induction s. unfoldRemoveNotsG. simpl. trivial. simpl in H. exfalso; omega. simpl in H. exfalso; omega. simpl in H. exfalso; omega. Qed. Ltac define s def := set (temp := def); remember temp as s; unfold temp in *; clear temp. Lemma notRec : forall (n : nat), (forall (m : nat), m <= n -> P m) -> P (S n). Proof. unfold P. intros. induction s; unfoldRemoveNotsG. unfold numNots. trivial. simpl in *. cut (numNots (removeNots s1) = 0 /\ numNots (removeNots s2) = 0). omega. split. apply (H (depth s1)); omega. apply (H (depth s2)); omega. simpl in *. cut (numNots (removeNots s1) = 0 /\ numNots (removeNots s2) = 0). omega. split. apply (H (depth s1)); omega. apply (H (depth s2)); omega. simpl in H0. induction s. destruct c; unfold notCheck; simpl; trivial; try solve[repeat findIf]. simpl in *. cut (numNots (removeNots (not s1)) = 0 /\ numNots (removeNots (not s2)) = 0). omega. split. apply (H (depth s1 + 1)). omega. simpl. auto. apply (H (depth s2 + 1)). omega. simpl. auto. simpl in *. cut (numNots (removeNots (not s1)) = 0 /\ numNots (removeNots (not s2)) = 0). omega. split. apply (H (depth s1 + 1)). omega. simpl. auto. apply (H (depth s2 + 1)). omega. simpl. auto. apply (H (depth s)). simpl in H0; omega. trivial. Qed. Lemma notAllDepth : forall (s :srac) (n:nat), P n. Proof. intros. apply (strongind P notBase notRec n). Qed. Lemma numNots0 : forall (s :srac), numNots(removeNots s) = 0. Proof. intros. apply (notAllDepth s (depth s)). trivial. Qed. Lemma removeIsntNot : forall (s1 s2 :srac), removeNots s1 = not s2 -> False. Proof. intros. generalize (numNots0 s1); intro. rewrite H in H0. simpl in H0. omega. Qed. Fixpoint numOrs (s:srac) : nat := match s with | single c n => 0 | or s1 s2 => 1 + numOrs s1 + numOrs s2 | and s1 s2 => numOrs s1 + numOrs s2 | not s1 => numOrs s1 end. Function moveOrUp (s:srac) {measure depth s} : srac := match s with | or s1 s2 => or (moveOrUp s1) (moveOrUp s2) | and s1 s2 => match (s1, s2) with | (single c1 z1, single c2 z2) => s | (not s3, _) => s (* shouldn't be any*) | (_, not s4) => s (* shouldn't be any*) | (or s3 s4, _) => or (moveOrUp (and s3 s2)) (moveOrUp (and s4 s2)) | (_, or s3 s4) => or (moveOrUp (and s1 s3)) (moveOrUp (and s1 s4)) | (single c1 z1, and s3 s4) => and s1 (moveOrUp s2) | (and s3 s4, single c2 z2) => and (moveOrUp s1) s2 | (and s3 s4, and s5 s6) => and (moveOrUp s1) (moveOrUp s2) end | single c n => s | not s1 => s (* shouldn't be any*) end. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. intros; simpl; omega. Defined. Ltac unfoldMoveOrUpG := match goal with | [ |- context[moveOrUp ?s] ] => generalize (moveOrUp_equation s); intro C; rewrite C in *; clear C end. Ltac unfoldMoveOrUp H := match type of H with | context[moveOrUp ?s] => generalize (moveOrUp_equation s); intro C; rewrite C in *; clear C end. Fixpoint numAndsOverOr (s:srac) : nat := match s with | or s1 s2 => numAndsOverOr s1 + numAndsOverOr s2 | and s1 s2 => numAndsOverOr s1 + numAndsOverOr s2 + numOrs s1 + numOrs s2 | _ => 0 end. Definition P2 (n: nat) : Prop := forall (ls : list reqValue) (s : srac), depth s = n -> sracMatch ls (removeNots s) = sracMatch ls s. Lemma notSoundBase : P2 0. Proof. unfold P2. intros. induction s; simpl in H; omega. Qed. Lemma notSoundRec : forall (n : nat), (forall m : nat, m <= n -> P2 m) -> P2 (S n). Proof. unfold P2. intros. destruct s; unfoldRemoveNotsG; trivial; simpl in *; try (assert (depth s1 <= n); [omega|]); try (assert (depth s2 <= n); [omega|]). cut ((sracMatch ls (removeNots s1) = sracMatch ls s1) /\ (sracMatch ls (removeNots s2) = sracMatch ls s2)). intro. destruct H3. rewrite H3, H4. trivial. split. apply (H (depth s1) H1 _ _); trivial. apply (H (depth s2) H2 _ _); trivial. assert (depth s1 = depth s1); [trivial |]. generalize (H (depth s1) H1 ls s1 H3); intro. rewrite H4. assert (depth s2 = depth s2); [trivial |]. generalize (H (depth s2) H2 ls s2 H5); intro. rewrite H6. trivial. destruct s. simpl in *. rewrite (notCheckSoundComp ls c n0). unfold sracMatch. trivial. simpl in *. assert (depth (not s1) <= n); [simpl; omega|]; assert (depth (not s2) <= n); [simpl; omega|]. cut ((sracMatch ls (removeNots (not s1)) = ~t sracMatch ls s1) /\ (sracMatch ls (removeNots (not s2)) = ~t sracMatch ls s2)). intro. destruct H3. rewrite H3, H4. destruct (sracMatch ls s1), (sracMatch ls s2); trivial. simpl. split. rewrite (H (depth (not s1)) H1 _ _); trivial. rewrite (H (depth (not s2)) H2 _ _); trivial. simpl in *. assert (depth (not s1) <= n); [simpl; omega|]; assert (depth (not s2) <= n); [simpl; omega|]. assert (depth (not s1) = depth (not s1)); [trivial |]. generalize (H (depth (not s1)) H1 ls (not s1) H3); intro. rewrite H4. assert (depth (not s2) = depth (not s2)); [trivial |]. generalize (H (depth (not s2)) H2 ls (not s2) H5); intro. rewrite H6. simpl. destruct (sracMatch ls s1), (sracMatch ls s2); trivial. simpl in *. rewrite notsCancel. apply (H (depth s)); omega. Qed. Lemma removeNotSoundComp : forall (ls : list reqValue) (s : srac), sracMatch ls (removeNots s) = sracMatch ls s. Proof. intros. generalize (strongind P2); intro. apply (H notSoundBase notSoundRec (depth s)). trivial. Qed. Fixpoint distr' (s1 s2 : srac) := match s2 with |or s3 s4 => or (distr' s1 s3) (distr' s1 s4) | _ => and s1 s2 end. Fixpoint distr (s1 s2 : srac) : srac := match s1 with | or s3 s4 => or (distr s3 s2) (distr s4 s2) | _ => distr' s1 s2 end. Lemma andOrDistributivity1: forall (s1 s2 s3: trilean), (s1 /\t (s2 \/t s3)) = ((s1 /\t s2) \/t (s1 /\t s3)). Proof. intros. destruct s1; destruct s2; destruct s3; simpl; trivial. Qed. Lemma andOrDistributivity2: forall (s1 s2 s3: trilean), ((s1 \/t s2) /\t s3) = ((s1 /\t s3) \/t (s2 /\t s3)). Proof. intros. destruct s1; destruct s2; destruct s3; simpl; trivial. Qed. Ltac distrAndOr := match goal with | [ |- context[(?s1 /\t (?s2 \/t ?s3))] ] => rewrite (andOrDistributivity1 s1 s2 s3) in * | [ |- context[((?s1 \/t ?s2) /\t ?s3)] ] => rewrite (andOrDistributivity2 s1 s2 s3) in * end. Lemma distr'SoundComp : forall (ls : list reqValue) (s1 s2 : srac), sracMatch ls (and s1 s2) = sracMatch ls (distr' s1 s2). Proof. intros. induction s2; simpl; trivial. distrAndOr. rewrite <- IHs2_1, <- IHs2_2. simpl; trivial. Qed. Lemma distrSoundComp : forall (ls : list reqValue) (s1 s2 : srac), sracMatch ls (and s1 s2) = sracMatch ls (distr s1 s2). Proof. intros. induction s1; simpl; try solve [rewrite <- distr'SoundComp; simpl; trivial]. rewrite <- IHs1_1, <- IHs1_2. distrAndOr. simpl; trivial. Qed. Lemma noOrs : forall s : srac, numOrs s = 0 -> numAndsOverOr s = 0. Proof. intros. induction s; simpl in *; trivial. rewrite IHs1, IHs2; omega. discriminate H. Qed. Lemma distr'DNF: forall (s1 s2 : srac), numOrs s1 = 0 -> numNots s2 = 0 -> numAndsOverOr s2 = 0 -> numAndsOverOr (distr' s1 s2) = 0. Proof. intros. induction s2; simpl in *. rewrite H, (noOrs s1); trivial. rewrite (noOrs s1); trivial. omega. rewrite IHs2_1, IHs2_2; trivial; omega. discriminate H0. Qed. Lemma distrDNF: forall (s1 s2 : srac), numNots s1 = 0 -> numAndsOverOr s1 = 0 -> numNots s2 = 0 -> numAndsOverOr s2 = 0 -> numAndsOverOr (distr s1 s2) = 0. Proof. intros. induction s1; simpl in *; try apply distr'DNF; trivial. simpl. omega. rewrite IHs1_1, IHs1_2; trivial; omega. discriminate H. Qed. Fixpoint DNF (s : srac) : srac := match s with | or s1 s2 => or (DNF s1) (DNF s2) | and s1 s2 => distr (DNF s1) (DNF s2) | _ => s end. Lemma DNFSoundComp: forall (ls : list reqValue) (s : srac), sracMatch ls s = sracMatch ls (DNF s). Proof. intros. induction s; simpl; trivial. rewrite <- distrSoundComp. simpl. rewrite IHs1, IHs2. trivial. rewrite IHs1, IHs2. trivial. Qed. Lemma numNotsDistr': forall (s1 s2 : srac), numNots s1 = 0 -> numNots s2 = 0 -> numNots (distr' s1 s2) = 0. Proof. intros. induction s2; simpl in *; try omega. rewrite IHs2_1, IHs2_2; omega. Qed. Lemma numNotsDNF: forall (s : srac), numNots s = 0 -> numNots (DNF s) = 0. Proof. intros. induction s; simpl; trivial. simpl in H. assert (numNots (DNF s1) = 0); [apply IHs1; omega |]. assert (numNots (DNF s2) = 0); [apply IHs2; omega |]. clear H IHs1 IHs2. induction (DNF s1); simpl; try apply numNotsDistr'; trivial. rewrite IHs1, IHs2; simpl in *; omega. rewrite IHs1, IHs2; simpl in *; omega. Qed. Definition convertDNF (s : srac) : srac := DNF (removeNots s). Lemma DNFisDNF: forall (s : srac), numNots s = 0 -> numAndsOverOr (DNF s) = 0. Proof. intros. induction s; simpl in *; trivial. apply distrDNF; try auto with *; apply numNotsDNF; omega. rewrite IHs1, IHs2; omega. Qed. Lemma DNFRemoveNotsisDNF: forall (s : srac), numAndsOverOr (convertDNF s) = 0. Proof. intros. unfold convertDNF. apply DNFisDNF. apply numNots0. Qed. Lemma convertDNFSoundComp: forall (ls : list reqValue) (s : srac), sracMatch ls s = sracMatch ls (convertDNF s). Proof. intros. unfold convertDNF. rewrite <- DNFSoundComp. rewrite removeNotSoundComp. trivial. Qed. Lemma numNotsConvertDNF: forall (s : srac), numNots (convertDNF s) = 0. Proof. intros. unfold convertDNF. apply numNotsDNF. apply numNots0. Qed. Fixpoint blt_nat (n m : nat) : bool := match n, m with | _, O => false | O, S m' => true | S n',S m' => blt_nat n' m' end. Fixpoint merge l1 l2 := let fix merge_aux l2 := match l1, l2 with | nil, _ => l2 | _, nil => l1 | (c1, n1)::l1', (c2, n2)::l2' => if blt_nat n1 n2 then (c1, n1) :: merge l1' l2 else if beq_nat n1 n2 then (coreCheck c1 c2, n1) :: merge l1' l2' else (c2, n2) :: merge_aux l2' end in merge_aux l2. Fixpoint split' (s : srac) : list (core * nat) := match s with | and s1 s2 => merge (split' s1) (split' s2) | single c n => (c, n) :: nil | _ => nil end. Fixpoint split (s : srac) : list (list (core * nat)) := match s with | or s1 s2 => split s1 ++ split s2 | _ => split' s :: nil end. Definition toLists (s :srac): list (list (core*nat)) := split (convertDNF s). Fixpoint lMatch (lc : list (core*nat)) (lr : list reqValue) : trilean := match lc with | nil => T | (c,n):: lc' => (coreMatch (nth n lr blank) c) /\t (lMatch lc' lr) end. Fixpoint llMatch (llc : list (list (core*nat))) (lr : list reqValue) : trilean := match llc with | nil => F | lc :: llc' => (lMatch lc lr) \/t (llMatch llc' lr) end. Lemma mergeNil : forall l : list (core * nat), merge nil l = l. Proof. intros. induction l; simpl; trivial. Qed. Lemma mergeNil2 : forall l : list (core * nat), merge l nil = l. Proof. intros. induction l; simpl; trivial. destruct a. trivial. Qed. Lemma andTrue : forall t : trilean, (t /\t T) = t. Proof. induction t; trivial. Qed. Definition P3' (n: nat) : Prop := forall (l1 l2 : list (core * nat)) (lr : list reqValue), length l1 + length l2 = n -> lMatch (merge l1 l2) lr = (lMatch l1 lr /\t lMatch l2 lr). Lemma mergeBase : P3' 0. Proof. unfold P3'. intros. destruct l1; destruct l2; simpl in *; trivial; omega. Qed. Lemma andAssoc : forall (t1 t2 t3: trilean), ((t1 /\t t2) /\t t3) = (t1 /\t (t2 /\t t3)). Proof. intros. destruct t1, t2, t3; simpl; trivial. Qed. Lemma mergeNoNils : forall (l1 l2 : list (core * nat)) (c1 c2 : core) (n1 n2 : nat), (merge ((c1, n1):: l1) ((c2, n2):: l2) = (c1, n1) :: merge l1 ((c2, n2):: l2)) \/ ((merge ((c1, n1):: l1) ((c2, n2):: l2) = (coreCheck c1 c2, n1) :: merge l1 l2) /\ (beq_nat n1 n2) = true) \/ (merge ((c1, n1):: l1) ((c2, n2):: l2) = (c2, n2) :: merge ((c1, n1):: l1) l2). Proof. intros. simpl. caseSimpl (blt_nat n1 n2); [left| right]. trivial. caseSimpl (beq_nat n1 n2); [left| right]. split; trivial. trivial. Qed. Lemma mergeRec : forall (n : nat), (forall m : nat, m <= n -> P3' m) -> P3' (S n). Proof. unfold P3'. intros. destruct l1. rewrite mergeNil. simpl. trivial. destruct l2. rewrite mergeNil2. unfold lMatch at 3. rewrite andTrue. trivial. destruct p, p0. generalize (mergeNoNils l1 l2 c c0 n0 n1); intro. destruct H1. rewrite H1. simpl. rewrite (H n); trivial. simpl. repeat (rewrite andAssoc). trivial. simpl in *; omega. destruct H1. destruct H1. rewrite H1. simpl. rewrite (H (pred n)). simpl. rewrite coreCheckCorrect. rewrite (beq_nat_true n0 n1); trivial. repeat (rewrite andAssoc). rewrite (trileanAndSym (lMatch l1 lr) (coreMatch (nth n1 lr blank) c0 /\t lMatch l2 lr)). repeat (rewrite andAssoc). rewrite (trileanAndSym (lMatch l1 lr) (lMatch l2 lr)). trivial. omega. simpl in *; omega. rewrite H1. unfold lMatch; fold lMatch. rewrite (H n); trivial. simpl. rewrite (trileanAndSym (coreMatch (nth n1 lr blank) c0) ((coreMatch (nth n0 lr blank) c /\t lMatch l1 lr) /\t lMatch l2 lr)). repeat (rewrite andAssoc). rewrite (trileanAndSym (lMatch l2 lr) (coreMatch (nth n1 lr blank) c0)). trivial. simpl in *; omega. Qed. Lemma mergeCorrect : forall (l1 l2 : list (core * nat)) (lr : list reqValue), lMatch (merge l1 l2) lr = (lMatch l1 lr /\t lMatch l2 lr). Proof. intros. generalize (strongind P3'); intro. apply (H mergeBase mergeRec (length (l1++l2))). symmetry. apply app_length. Qed. Definition P3 (n: nat) : Prop := forall (s : srac) (lr : list reqValue), depth s = n -> numNots s = 0 -> numOrs s = 0 -> lMatch (split' s) lr = sracMatch lr s. Lemma listCorrectBase : P3 0. Proof. unfold P3. intros. destruct s; simpl in *; try omega. Qed. Lemma listCorrectRec : forall (n : nat), (forall m : nat, m <= n -> P3 m) -> P3 (S n). Proof. unfold P3. intros. destruct s; simpl in *; try omega. rewrite andTrue; trivial. assert (depth s1 <= n /\ depth s2 <= n); [omega|]. destruct H3. rewrite <- (H (depth s1) H3 s1); try omega. rewrite <- (H (depth s2) H4 s2); try omega. apply mergeCorrect. Qed. Lemma listCorrect : forall (s : srac) (lr : list reqValue), numNots s = 0 -> numOrs s = 0 -> lMatch (split' s) lr = sracMatch lr s. Proof. intros. generalize (strongind P3); intro. apply (H1 listCorrectBase listCorrectRec (depth s)); trivial. Qed. Lemma llCorrect:forall (s : srac) (lr : list reqValue), llMatch (toLists s) lr = sracMatch lr s. Proof. intros. unfold toLists. rewrite convertDNFSoundComp. generalize (DNFRemoveNotsisDNF s); intro. generalize (numNotsConvertDNF s); intro. induction (convertDNF s); simpl. destruct (coreMatch (nth n lr blank) c); trivial. simpl in H, H0. rewrite mergeCorrect. rewrite listCorrect; try omega. rewrite listCorrect; try omega. destruct (sracMatch lr s0_1 /\t sracMatch lr s0_2); trivial. simpl in H, H0. rewrite <- IHs0_1; try omega. rewrite <- IHs0_2; try omega. clear. induction (split s0_1). simpl; trivial. simpl. rewrite IHl. destruct (lMatch a lr); destruct (llMatch l lr); destruct (llMatch (split s0_2) lr); simpl; trivial. simpl in H0. discriminate H0. Qed.