Theory Second_Example

chapter ‹Second Example›

theory %invisible Second_Example
imports Main
begin

text ‹\label{chap:exampleII}›

text ‹Pop-refinement is illustrated via a simple derivation,
in Isabelle/HOL,
of a non-deterministic program that satisfies a hyperproperty.›


section ‹Hyperproperties›
text ‹\label{sec:hyper}›

text ‹Hyperproperties are predicates over sets of traces~%
cite"ClarksonSchneiderHyperproperties".
Hyperproperties capture security policies
like non-interference~cite"GoguenMeseguerNonInterference",
which applies to deterministic systems,
and generalized non-interference (GNI)~cite"McCulloughSpecificationsMLS",
which generalizes non-interference to non-deterministic systems.›

text ‹The formulation of GNI in~cite"ClarksonSchneiderHyperproperties",
which is derived from~cite"McLeanPossibilisticProperties",
is based on:
\begin{itemize}
\item
A notion of traces as infinite streams of abstract states.
\item
Functions that map each state to low and high inputs and outputs,
where `low' and `high' have the usual security meaning
(e.g.\ `low' means `unclassified' and `high' means `classified').
These functions are homomorphically extended to map each trace
to infinite streams of low and high inputs and outputs.
\end{itemize}
The following formulation is slightly more general,
because the functions that return low and high inputs and outputs
operate directly on abstract traces.›

text ‹GNI says that for any two traces @{term τ1} and @{term τ2},
there is always a trace @{term τ3} with
the same high inputs as @{term τ1}
and the same low inputs and low outputs as @{term τ2}.
Intuitively, this means that a low observer
(i.e.\ one that only observes low inputs and low outputs of traces)
cannot gain any information about high inputs
(i.e.\ high inputs cannot interfere with low outputs)
because observing a trace @{term τ2}
is indistinguishable from observing some other trace @{term τ3}
that has the same high inputs as an arbitrary trace @{term τ1}.›

locale generalized_non_interference =
  fixes low_in :: "  'i" ― ‹low inputs›
  fixes low_out :: "  'o" ― ‹low outputs›
  fixes high_in :: "  'i" ― ‹high inputs›
  fixes high_out :: "  'o" ― ‹high outputs›

definition (in generalized_non_interference) GNI :: " set  bool"
where "GNI 𝒯 
  τ1  𝒯. τ2  𝒯. τ3  𝒯.
    high_in τ3 = high_in τ1  low_in τ3 = low_in τ2  low_out τ3 = low_out τ2"


section ‹Target Programming Language›
text ‹\label{sec:targetII}›

text ‹In the target language used in this example,%
\footnote{Even though this language has many similarities
with the language in \secref{sec:targetI},
the two languages are defined separately
to keep \chapref{chap:exampleI} simpler.}
a program consists of
a list of distinct state variables
and a body of statements.
The statements modify the variables
by deterministically assigning results of expressions
and by non-deterministically assigning random values.
Expressions are built out of
non-negative integer constants,
state variables,
and addition operations.
Statements are combined
via conditionals, whose tests compare expressions for equality,
and via sequencing.
Each variable stores a non-negative integer.
Executing the body in a state yields a new state.
Because of non-determinism, different new states are possible,
i.e.\ executing the body in the same state
may yield different new states at different times.›

text ‹For instance, executing the body of the program
\begin{verbatim}
  prog {
    vars {
      x
      y
    }
    body {
      if (x == y + 1) {
        x = 0;
      } else {
        x = y + 3;
      }
      randomize y;
      y = y + 2;
    }
  }
\end{verbatim}
in the state where \verb|x| contains 4 and \verb|y| contains 7,
yields a new state where
\verb|x| always contains 10
and \verb|y| may contain any number in $\{2,3,\ldots\}$.›


subsection ‹Syntax›
text ‹\label{sec:syntaxII}›

text ‹Variables are identified by names.›

type_synonym name = string

text ‹Expressions are built out of
constants, variables, and addition operations.›

datatype expr = Const nat | Var name | Add expr expr

text ‹Statements are built out of
deterministic assignments,
non-deterministic assignments,
conditionals,
and sequencing.›

datatype stmt =
  Assign name expr |
  Random name |
  IfEq expr expr stmt stmt |
  Seq stmt stmt

text ‹A program consists of
a list of state variables
and a body statement.›

record prog =
  vars :: "name list"
  body :: stmt


subsection ‹Static Semantics›
text ‹\label{sec:staticII}›

text ‹A context is a set of variables.›

type_synonym ctxt = "name set"

text ‹Given a context,
an expression is well-formed iff\
all its variables are in the context.›

fun wfe :: "ctxt  expr  bool"
where
  "wfe Γ (Const c)  True" |
  "wfe Γ (Var v)  v  Γ" |
  "wfe Γ (Add e1 e2)  wfe Γ e1  wfe Γ e2"

text ‹Given a context,
a statement is well-formed iff\
its deterministic assignments
assign well-formed expressions to variables in the context,
its non-deterministic assignments operate on variables in the context,
and its conditional tests compare well-formed expressions.›

fun wfs :: "ctxt  stmt  bool"
where
  "wfs Γ (Assign v e)  v  Γ  wfe Γ e" |
  "wfs Γ (Random v)  v  Γ" |
  "wfs Γ (IfEq e1 e2 s1 s2)  wfe Γ e1  wfe Γ e2  wfs Γ s1  wfs Γ s2" |
  "wfs Γ (Seq s1 s2)  wfs Γ s1  wfs Γ s2"

text ‹The context of a program consists of the state variables.›

definition ctxt :: "prog  ctxt"
where "ctxt p  set (vars p)"

text ‹A program is well-formed iff\
the variables are distinct
and the body is well-formed in the context of the program.›

definition wfp :: "prog  bool"
where "wfp p  distinct (vars p)  wfs (ctxt p) (body p)"


subsection ‹Dynamic Semantics›
text ‹\label{sec:dynamicII}›

text ‹A state associates values (non-negative integers) to variables.›

type_synonym state = "name  nat"

text ‹A state matches a context iff\
state and context have the same variables.›

definition match :: "state  ctxt  bool"
where "match σ Γ  dom σ = Γ"

text ‹Evaluating an expression in a state yields a value,
or an error (@{const None})
if the expression contains a variable not in the state.›

definition add_opt :: "nat option  nat option  nat option" (infixl "" 65)
― ‹Lifting of addition to @{typ "nat option"}.›
where "U1  U2 
  case (U1, U2) of (Some u1, Some u2)  Some (u1 + u2) | _  None"

fun eval :: "state  expr  nat option"
where
 "eval σ (Const c) = Some c" |
 "eval σ (Var v) = σ v" |
 "eval σ (Add e1 e2) = eval σ e1  eval σ e2"

text ‹Evaluating a well-formed expression never yields an error,
if the state matches the context.›

lemma eval_wfe:
  "wfe Γ e  match σ Γ  eval σ e  None"
by (induct e, auto simp: match_def add_opt_def)

text ‹Executing a statement in a state yields a new state,
or an error (@{const None})
if the evaluation of an expression yields an error
or if an assignment operates on a variable not in the state.
Non-determinism is modeled via a relation
between old states and results,
where a result is either a new state or an error.›

inductive exec :: "stmt  state  state option  bool"
  ("_  _  _" [50, 50, 50] 50)
where
  ExecAssignNoVar:
    "v  dom σ  Assign v e  σ  None" |
  ExecAssignEvalError:
    "eval σ e = None  Assign v e  σ  None" |
  ExecAssignOK: "
    v  dom σ 
    eval σ e = Some u 
    Assign v e  σ  Some (σ(v  u))" |
  ExecRandomNoVar:
    "v  dom σ  Random v  σ  None" |
  ExecRandomOK:
    "v  dom σ  Random v  σ  Some (σ(v  u))" |
  ExecCondEvalError1:
    "eval σ e1 = None  IfEq e1 e2 s1 s2  σ  None" |
  ExecCondEvalError2:
    "eval σ e2 = None  IfEq e1 e2 s1 s2  σ  None" |
  ExecCondTrue: "
    eval σ e1 = Some u1 
    eval σ e2 = Some u2 
    u1 = u2 
    s1  σ  ρ 
    IfEq e1 e2 s1 s2  σ  ρ" |
  ExecCondFalse: "
    eval σ e1 = Some u1 
    eval σ e2 = Some u2 
    u1  u2 
    s2  σ  ρ 
    IfEq e1 e2 s1 s2  σ  ρ" |
  ExecSeqError:
    "s1  σ  None  Seq s1 s2  σ  None" |
  ExecSeqOK:
    "s1  σ  Some σ'  s2  σ'  ρ  Seq s1 s2  σ  ρ"

text ‹The execution of any statement in any state always yields a result.›

lemma exec_always:
  "ρ. s  σ  ρ"
proof (induct s arbitrary: σ)
  case Assign
  show ?case
  by (metis
    ExecAssignEvalError ExecAssignNoVar ExecAssignOK option.exhaust)
next
  case Random
  show ?case
  by (metis ExecRandomNoVar ExecRandomOK)
next
  case IfEq
  thus ?case
  by (metis
    ExecCondEvalError1 ExecCondEvalError2 ExecCondFalse ExecCondTrue
    option.exhaust)
next
  case Seq
  thus ?case
  by (metis ExecSeqError ExecSeqOK option.exhaust)
qed

text ‹Executing a well-formed statement in a state that matches the context
never yields an error and always yields states that match the context.›

lemma exec_wfs_match:
  "wfs Γ s  match σ Γ  s  σ  Some σ'  match σ' Γ"
proof (induct s arbitrary: σ σ')
  case (Assign v e)
  then obtain u
  where "eval σ e = Some u"
  and "σ' = σ(v  u)"
  by (auto elim: exec.cases)
  with Assign
  show ?case
  by (metis
    domIff dom_fun_upd fun_upd_triv match_def option.distinct(1) wfs.simps(1))
next
  case (Random v)
  then obtain u
  where "σ' = σ(v  u)"
  by (auto elim: exec.cases)
  with Random
  show ?case
  by (metis
    domIff dom_fun_upd fun_upd_triv match_def option.distinct(1) wfs.simps(2))
next
  case (IfEq e1 e2 s1 s2)
  hence "s1  σ  Some σ'  s2  σ  Some σ'"
  by (blast elim: exec.cases)
  with IfEq
  show ?case
  by (metis wfs.simps(3))
next
  case (Seq s1 s2)
  then obtain σi
  where "s1  σ  Some σi"
  and "s2  σi  Some σ'"
  by (blast elim: exec.cases)
  with Seq
  show ?case
  by (metis wfs.simps(4))
qed

lemma exec_wfs_no_error:
  "wfs Γ s  match σ Γ  ¬ (s  σ  None)"
proof (induct s arbitrary: σ)
  case (Assign v e)
  hence Var: "v  dom σ"
  by (auto simp: match_def)
  from Assign
  have "eval σ e  None"
  by (metis eval_wfe wfs.simps(1))
  with Var
  show ?case
  by (auto elim: exec.cases)
next
  case (Random v)
  thus ?case
  by (auto simp: match_def elim: exec.cases)
next
  case (IfEq e1 e2 s1 s2)
  then obtain u1 u2
  where "eval σ e1 = Some u1"
  and "eval σ e2 = Some u2"
  by (metis eval_wfe not_Some_eq wfs.simps(3))
  with IfEq
  show ?case
  by (auto elim: exec.cases)
next
  case (Seq s1 s2)
  show ?case
  proof
    assume "Seq s1 s2  σ  None"
    hence "s1  σ  None  (σ'. s1  σ  Some σ'  s2  σ'  None)"
    by (auto elim: exec.cases)
    with Seq exec_wfs_match
    show False
    by (metis wfs.simps(4))
  qed
qed

lemma exec_wfs_always_match:
  "wfs Γ s  match σ Γ  σ'. s  σ  Some σ'  match σ' Γ"
by (metis exec_always exec_wfs_match exec_wfs_no_error option.exhaust)

text ‹The states of a program
are the ones that match the context of the program.›

definition states :: "prog  state set"
where "states p  {σ. match σ (ctxt p)}"

text ‹Executing the body of a well-formed program in a state of the program
always yields some state of the program, and never an error.›

lemma exec_wfp_no_error:
  "wfp p  σ  states p  ¬ (body p  σ  None)"
by (metis exec_wfs_no_error mem_Collect_eq states_def wfp_def)

lemma exec_wfp_in_states:
  "wfp p  σ  states p  body p  σ  Some σ'  σ'  states p"
by (metis exec_wfs_match mem_Collect_eq states_def wfp_def)

lemma exec_wfp_always_in_states:
  "wfp p  σ  states p  σ'. body p  σ  Some σ'  σ'  states p"
by (metis exec_always exec_wfp_in_states exec_wfp_no_error option.exhaust)

text ‹Program execution can be described
in terms of the trace formalism in~cite"ClarksonSchneiderHyperproperties".
Every possible (non-erroneous) execution of a program
can be described by a trace of two states---initial and final.
In this definition,
erroneous executions do not contribute to the traces of a program;
only well-formed programs are of interest,
which, as proved above, never execute erroneously.
Due to non-determinism, there may be traces
with the same initial state and different final states.›

record trace =
  initial :: state
  final :: state

inductive_set traces :: "prog  trace set"
for p::prog
where [intro!]: "
  σ  states p 
  body p  σ  Some σ' 
  initial = σ, final = σ'  traces p"

text ‹The finite traces of a program could be turned into infinite traces
by infinitely stuttering the final state,
obtaining the `executions' defined in~cite"ClarksonSchneiderHyperproperties".
However, such infinite traces carry no additional information
compared to the finite traces from which they are derived:
for programs in this language,
the infinite executions of~cite"ClarksonSchneiderHyperproperties"
are modeled as finite traces of type @{typ trace}.›


section ‹Requirement Specification›
text ‹\label{sec:specificationII}›

text ‹The target program
must process low and high inputs to yield low and high outputs,
according to constraints that involve
both non-determinism and under-specification,
with no information flowing from high inputs to low outputs.%
\footnote{As in \secref{sec:hyper},
`low' and `high' have the usual security meaning,
e.g.\ `low' means `unclassified' and `high' means `classified'.}›


subsection ‹Input/Output Variables›
text ‹\label{sec:specII:iovars}›

text ‹Even though the language defined in \secref{sec:targetII}
has no explicit features for input and output,
an external agent could
write values into some variables,
execute the program body,
and read values from some variables.
Thus, variables may be regarded as holding
inputs (in the initial state) and outputs (in the final state).›

text ‹In the target program, four variables are required:
\begin{itemize}
\item
A variable @{term "''lowIn''"} to hold low inputs.
\item
A variable @{term "''lowOut''"} to hold low outputs.
\item
A variable @{term "''highIn''"} to hold high inputs.
\item
A variable @{term "''highOut''"} to hold high outputs.
\end{itemize}
Other variables are allowed but not required.›

definition io_vars :: "prog  bool"
where "io_vars p  ctxt p  {''lowIn'', ''lowOut'', ''highIn'', ''highOut''}"


subsection ‹Low Processing›
text ‹\label{sec:specII:lowproc}›

text ‹If the low input is not 0,
the low output must be 1 plus the low input.
That is,
for every possible execution of the program
where the initial state's low input is not 0,
the final state's low output must be 1 plus the low input.
If there are multiple possible final states for the same initial state
due to non-determinism,
all of them must have the same required low output.
Thus, processing of non-0 low inputs must be deterministic.›

definition low_proc_non0 :: "prog  bool"
where "low_proc_non0 p 
  σ  states p. σ'.
    the (σ ''lowIn'')  0 
    body p  σ  Some σ' 
    the (σ' ''lowOut'') = the (σ ''lowIn'') + 1"

text ‹If the low input is 0, the low output must be a random value.
That is,
for every possible initial state of the program whose low input is 0,
and for every possible value,
there must exist an execution of the program
whose final state has that value as low output.
Executions corresponding to all possible values must be possible.
Thus, processing of the 0 low input must be non-deterministic.›

definition low_proc_0 :: "prog  bool"
where "low_proc_0 p 
  σ  states p. u.
    the (σ ''lowIn'') = 0 
    (σ'. body p  σ  Some σ'  the (σ' ''lowOut'') = u)"


subsection ‹High Processing›
text ‹\label{sec:specII:highproc}›

text ‹The high output must be
at least as large as the sum of the low and high inputs.
That is,
for every possible execution of the program,
the final state's high output must satisfy the constraint.
If there are multiple possible final states for the same initial state
due to non-determinism,
all of them must contain a high output that satisfies the constraint.
Since different high outputs may satisfy the constraint given the same inputs,
not all the possible final states from a given initial state
must have the same high output.
Thus, processing of high inputs is under-specified;
it can be realized deterministically or non-deterministically.›

definition high_proc :: "prog  bool"
where "high_proc p 
  σ  states p. σ'.
    body p  σ  Some σ' 
    the (σ' ''highOut'')  the (σ ''lowIn'') + the (σ ''highIn'')"


subsection ‹All Requirements›
text ‹\label{sec:specII:all}›

text ‹Besides satisfying the above requirements on
input/output variables, low processing, and high processing,
the target program must be well-formed.›

definition spec0 :: "prog  bool"
where "spec0 p 
  wfp p  io_vars p  low_proc_non0 p  low_proc_0 p  high_proc p"


subsection ‹Generalized Non-Interference›
text ‹\label{sec:specII:GNI}›

text ‹The parameters of the GNI formulation in \secref{sec:hyper}
are instantiated according to the target program under consideration.
In an execution of the program:
\begin{itemize}
\item
The value of the variable @{term "''lowIn''"} in the initial state
is the low input.
\item
The value of the variable @{term "''lowOut''"} in the final state
is the low output.
\item
The value of the variable @{term "''highIn''"} in the initial state
is the high input.
\item
The value of the variable @{term "''highOut''"} in the final state
is the high output.
\end{itemize}›

definition low_in :: "trace  nat"
where "low_in τ  the (initial τ ''lowIn'')"

definition low_out :: "trace  nat"
where "low_out τ  the (final τ ''lowOut'')"

definition high_in :: "trace  nat"
where "high_in τ  the (initial τ ''highIn'')"

definition high_out :: "trace  nat"
where "high_out τ  the (final τ ''highOut'')"

interpretation
  Target: generalized_non_interference low_in low_out high_in high_out .

abbreviation GNI :: "trace set  bool"
where "GNI  Target.GNI"

text ‹The requirements in @{const spec0} imply that
the set of traces of the target program satisfies GNI.›

lemma spec0_GNI:
  "spec0 p  GNI (traces p)"
proof (auto simp: Target.GNI_def)
  assume Spec: "spec0 p"
  ― ‹Consider a trace @{text τ1} and its high input:›
  fix τ1::trace
  define highIn where "highIn = high_in τ1"
  ― ‹Consider a trace @{text τ2},
        its low input and output,
        and its states:›
  fix τ2::trace
  define lowIn lowOut σ2 σ2'
    where "lowIn = low_in τ2"
      and "lowOut = low_out τ2"
      and "σ2 = initial τ2"
      and "σ2' = final τ2"
  assume "τ2  traces p"
  hence Exec2: "body p  σ2  Some σ2'"
  and State2: "σ2  states p"
  by (auto simp: σ2_def σ2'_def elim: traces.cases)
  ― ‹Construct the initial state of the witness trace @{text τ3}:›
  define σ3 where "σ3 = σ2 (''highIn''  highIn)"
  hence LowIn3: "the (σ3 ''lowIn'') = lowIn"
  and HighIn3: "the (σ3 ''highIn'') = highIn"
  by (auto simp: lowIn_def low_in_def σ2_def)
  from Spec State2
  have State3: "σ3  states p"
  by (auto simp: σ3_def states_def match_def spec0_def io_vars_def)
  ― ‹Construct the final state of @{text τ3}, and @{text τ3},
        by cases on @{term lowIn}:›
  show
    "τ3  traces p.
      high_in τ3 = high_in τ1 
      low_in τ3 = low_in τ2 
      low_out τ3 = low_out τ2"
  proof (cases lowIn)
    case 0
    ― ‹Use as final state the one required by @{term low_proc_0}:›
    with Spec State3 LowIn3
    obtain σ3'
    where Exec3: "body p  σ3  Some σ3'"
    and LowOut3: "the (σ3' ''lowOut'') = lowOut"
    by (auto simp: spec0_def low_proc_0_def)
    ― ‹Construct @{text τ3} from its initial and final states:›
    define τ3 where "τ3 = initial = σ3, final = σ3'"
    with Exec3 State3
    have Trace3: "τ3  traces p"
    by auto
    have "high_in τ3 = high_in τ1"
    and "low_in τ3 = low_in τ2"
    and "low_out τ3 = low_out τ2"
    by (auto simp:
      high_in_def low_in_def low_out_def
      τ3_def σ2_def σ2'_def
      highIn_def lowIn_def lowOut_def
      LowIn3 HighIn3 LowOut3)
    with Trace3
    show ?thesis
    by auto
  next
    case Suc
    hence Not0: "lowIn  0"
    by auto
    ― ‹Derive @{term τ2}'s low output from @{term low_proc_non0}:›
    with Exec2 State2 Spec
    have LowOut2: "lowOut = lowIn + 1"
    by (auto simp:
      spec0_def low_proc_non0_def σ2_def σ2'_def
      low_in_def low_out_def lowIn_def lowOut_def)
    ― ‹Use any final state for @{text τ3}:›
    from Spec
    have "wfp p"
    by (auto simp: spec0_def)
    with State3
    obtain σ3'
    where Exec3: "body p  σ3  Some σ3'"
    by (metis exec_always exec_wfp_no_error not_Some_eq)
    ― ‹Derive @{text τ3}'s low output from @{term low_proc_non0}:›
    with State3 Spec Not0
    have LowOut3: "the (σ3' ''lowOut'') = lowIn + 1"
    by (auto simp: spec0_def low_proc_non0_def LowIn3)
    ― ‹Construct @{text τ3} from its initial and final states:›
    define τ3 where "τ3 = initial = σ3, final = σ3'"
    with Exec3 State3
    have Trace3: "τ3  traces p"
    by auto
    have "high_in τ3 = high_in τ1"
    and "low_in τ3 = low_in τ2"
    and "low_out τ3 = low_out τ2"
    by (auto simp:
      high_in_def low_in_def low_out_def
      τ3_def σ3_def σ2_def
      LowOut2 LowOut3
      highIn_def lowOut_def[unfolded low_out_def, symmetric])
    with Trace3
    show ?thesis
    by auto
  qed
qed

text ‹Since GNI is implied by @{const spec0}
and since every pop-refinement of @{const spec0} implies @{const spec0},
GNI is preserved through every pop-refinement of @{const spec0}.
Pop-refinement differs from the popular notion of refinement
as inclusion of sets of traces (e.g.~cite"AbadiLamportRefinement"),
which does not preserve GNI~cite"ClarksonSchneiderHyperproperties".›


section ‹Stepwise Refinement›
text ‹\label{sec:refinementII}›

text ‹The remark at the beginning of \secref{sec:refinementI}
applies here as well:
the following sequence of refinement steps
may be overkill for obtaining an implementation of @{const spec0},
but illustrates concepts that should apply to more complex cases.›


subsection ‹Step 1›

text ‹\label{sec:refII:stepI}›

text ‹The program needs no other variables
besides those prescribed by @{const io_vars}.
Thus, @{const io_vars} is refined to a stronger condition
that constrains the program to contain exactly those variables,
in a certain order.›

abbreviation vars0 :: "name list"
where "vars0  [''lowIn'', ''lowOut'', ''highIn'', ''highOut'']"
― ‹The order of the variables in the list is arbitrary.›

lemma vars0_correct:
  "vars p = vars0  io_vars p"
by (auto simp: io_vars_def ctxt_def)

text ‹The refinement of @{const io_vars}
reduces the well-formedness of the program
to the well-formedness of the body.›

abbreviation Γ0 :: ctxt
where "Γ0  {''lowIn'', ''lowOut'', ''highIn'', ''highOut''}"

lemma reduce_wf_prog_to_body:
  "vars p = vars0  wfp p  wfs Γ0 (body p)"
by (auto simp: wfp_def ctxt_def)

text ‹The refinement of @{const io_vars}
induces a simplification of the processing constraints:
since the context of the program is now defined to be @{const Γ0},
the @{term "σ  states p"} conditions are replaced
with @{term "match σ Γ0"} conditions.›

definition low_proc_non01 :: "prog  bool"
where "low_proc_non01 p 
  σ σ'.
    match σ Γ0 
    the (σ ''lowIn'')  0 
    body p  σ  Some σ' 
    the (σ' ''lowOut'') = the (σ ''lowIn'') + 1"

lemma low_proc_non01_correct:
  "vars p = vars0  low_proc_non01 p  low_proc_non0 p"
by (auto simp: low_proc_non01_def low_proc_non0_def states_def ctxt_def)

definition low_proc_01 :: "prog  bool"
where "low_proc_01 p 
  σ u.
    match σ Γ0 
    the (σ ''lowIn'') = 0 
    (σ'. body p  σ  Some σ'  the (σ' ''lowOut'') = u)"

lemma low_proc_01_correct:
  "vars p = vars0  low_proc_01 p  low_proc_0 p"
by (auto simp: low_proc_01_def low_proc_0_def states_def ctxt_def)

definition high_proc1 :: "prog  bool"
where "high_proc1 p 
  σ σ'.
    match σ Γ0 
    body p  σ  Some σ' 
    the (σ' ''highOut'')  the (σ ''lowIn'') + the (σ ''highIn'')"

lemma high_proc1_correct:
  "vars p = vars0  high_proc1 p  high_proc p"
by (auto simp: high_proc1_def high_proc_def states_def ctxt_def)

text ‹The refinement of @{const spec0} consists of
the refinement of @{const io_vars} and of the simplified constraints.›

definition spec1 :: "prog  bool"
where "spec1 p 
  vars p = vars0 
  wfs Γ0 (body p) 
  low_proc_non01 p 
  low_proc_01 p 
  high_proc1 p"

lemma step_1_correct:
  "spec1 p  spec0 p"
by (auto simp:
  spec1_def spec0_def
  vars0_correct
  reduce_wf_prog_to_body
  low_proc_non01_correct
  low_proc_01_correct
  high_proc1_correct)


subsection ‹Step 2›

text ‹\label{sec:refII:stepII}›

text ‹The body of the target program is split
into two sequential statements---%
one to compute the low output and one to compute the high output.›

definition body_split :: "prog  stmt  stmt  bool"
where "body_split p sL sH  body p = Seq sL sH"
― ‹The order of the two statements in the body is arbitrary.›

text ‹The splitting reduces the well-formedness of the body
to the well-formedness of the two statements.›

lemma reduce_wf_body_to_stmts:
  "body_split p sL sH  wfs Γ0 (body p)  wfs Γ0 sL  wfs Γ0 sH"
by (auto simp: body_split_def)

text ‹The processing predicates over programs
are refined to predicates over the statements @{term sL} and @{term sH}.
Since @{term sH} follows @{term sL}:
\begin{itemize}
\item
@{term sH} must not change the low output, which is computed by @{term sL}.
\item
@{term sL} must not change the low and high inputs, which are used by @{term sH}.
\end{itemize}›

definition low_proc_non02 :: "stmt  bool"
where "low_proc_non02 sL 
  σ σ'.
    match σ Γ0 
    the (σ ''lowIn'')  0 
    sL  σ  Some σ' 
    the (σ' ''lowOut'') = the (σ ''lowIn'') + 1"

definition low_proc_02 :: "stmt  bool"
where "low_proc_02 sL 
  σ u.
    match σ Γ0 
    the (σ ''lowIn'') = 0 
    (σ'. sL  σ  Some σ'  the (σ' ''lowOut'') = u)"

definition low_proc_no_input_change :: "stmt  bool"
where "low_proc_no_input_change sL 
  σ σ'.
    match σ Γ0 
    sL  σ  Some σ' 
    the (σ' ''lowIn'') = the (σ ''lowIn'') 
    the (σ' ''highIn'') = the (σ ''highIn'')"

definition high_proc2 :: "stmt  bool"
where "high_proc2 sH 
  σ σ'.
    match σ Γ0 
    sH  σ  Some σ' 
    the (σ' ''highOut'')  the (σ ''lowIn'') + the (σ ''highIn'')"

definition high_proc_no_low_output_change :: "stmt  bool"
where "high_proc_no_low_output_change sH 
  σ σ'.
    match σ Γ0 
    sH  σ  Some σ' 
    the (σ' ''lowOut'') = the (σ ''lowOut'')"

lemma proc2_correct:
  assumes Body: "body_split p sL sH"
  assumes WfLow: "wfs Γ0 sL"
  assumes WfHigh: "wfs Γ0 sH"
  assumes LowNon0: "low_proc_non02 sL"
  assumes Low0: "low_proc_02 sL"
  assumes LowSame: "low_proc_no_input_change sL"
  assumes High: "high_proc2 sH"
  assumes HighSame: "high_proc_no_low_output_change sH"
  shows "low_proc_non01 p  low_proc_01 p  high_proc1 p"
proof (auto, goal_cases)
  ― ‹Processing of non-0 low input:›
  case 1
  show ?case
  proof (auto simp: low_proc_non01_def)
    fix σ σ'
    assume "body p  σ  Some σ'"
    with Body
    obtain σi
    where ExecLow: "sL  σ  Some σi"
    and ExecHigh: "sH  σi  Some σ'"
    by (auto simp: body_split_def elim: exec.cases)
    assume Non0: "the (σ ''lowIn'') > 0"
    assume InitMatch: "match σ Γ0"
    with ExecLow WfLow
    have "match σi Γ0"
    by (auto simp: exec_wfs_match)
    with Non0 InitMatch ExecLow ExecHigh HighSame LowNon0
    show "the (σ' ''lowOut'') = Suc (the (σ ''lowIn''))"
    unfolding high_proc_no_low_output_change_def low_proc_non02_def
    by (metis Suc_eq_plus1 gr_implies_not0)
  qed
next
  ― ‹Processing of 0 low input:›
  case 2
  show ?case
  proof (auto simp: low_proc_01_def)
    fix σ u
    assume InitMatch: "match σ Γ0"
    and "the (σ ''lowIn'') = 0"
    with Low0
    obtain σi
    where ExecLow: "sL  σ  Some σi"
    and LowOut: "the (σi ''lowOut'') = u"
    by (auto simp: low_proc_02_def)
    from InitMatch ExecLow WfLow
    have MidMatch: "match σi Γ0"
    by (auto simp: exec_wfs_match)
    with WfHigh
    obtain σ'
    where ExecHigh: "sH  σi  Some σ'"
    by (metis exec_wfs_always_match)
    with HighSame MidMatch
    have "the (σ' ''lowOut'') = the (σi ''lowOut'')"
    by (auto simp: high_proc_no_low_output_change_def)
    with ExecLow ExecHigh Body LowOut
    show "σ'. body p  σ  Some σ'  the (σ' ''lowOut'') = u"
    by (auto simp add: body_split_def dest: ExecSeqOK)
  qed
next
  ― ‹Processing of high input:›
  case 3
  show ?case
  proof (auto simp: high_proc1_def)
    fix σ σ'
    assume "body p  σ  Some σ'"
    with Body
    obtain σi
    where ExecLow: "sL  σ  Some σi"
    and ExecHigh: "sH  σi  Some σ'"
    by (auto simp: body_split_def elim: exec.cases)
    assume InitMatch: "match σ Γ0"
    with ExecLow WfLow
    have "match σi Γ0"
    by (auto simp: exec_wfs_match)
    with InitMatch ExecLow ExecHigh LowSame High
    show "the (σ' ''highOut'')  the (σ ''lowIn'') + the (σ ''highIn'')"
    unfolding low_proc_no_input_change_def high_proc2_def
    by metis
  qed
qed

text ‹The refined specification consists of
the splitting of the body into the two sequential statements
and the refined well-formedness and processing constraints.›

definition spec2 :: "prog  bool"
where "spec2 p 
  vars p = vars0 
  (sL sH.
    body_split p sL sH 
    wfs Γ0 sL 
    wfs Γ0 sH 
    low_proc_non02 sL 
    low_proc_02 sL 
    low_proc_no_input_change sL 
    high_proc2 sH 
    high_proc_no_low_output_change sH)"

lemma step_2_correct:
  "spec2 p  spec1 p"
by (auto simp: spec2_def spec1_def reduce_wf_body_to_stmts proc2_correct)


subsection ‹Step 3›

text ‹\label{sec:refII:stepIII}›

text ‹The processing constraints
@{const low_proc_non02} and @{const low_proc_02} on @{term sL}
suggest the use of a conditional that
randomizes @{term "''lowOut''"} if @{term "''lowIn''"} is 0,
and stores 1 plus @{term "''lowIn''"} into @{term "''lowOut''"} otherwise.›

abbreviation sL0 :: stmt
where "sL0 
  IfEq
    (Var ''lowIn'')
    (Const 0)
    (Random ''lowOut'')
    (Assign ''lowOut'' (Add (Var ''lowIn'') (Const 1)))"

lemma wfs_sL0:
  "wfs Γ0 sL0"
by auto

lemma low_proc_non0_sL0:
  "low_proc_non02 sL0"
proof (auto simp only: low_proc_non02_def)
  fix σ σ'
  assume Match: "match σ Γ0"
  assume "sL0  σ  Some σ'"
  and "the (σ ''lowIn'') > 0"
  hence "(Assign ''lowOut'' (Add (Var ''lowIn'') (Const 1)))  σ  Some σ'"
  by (auto elim: exec.cases)
  hence "σ' = σ (''lowOut''  the (eval σ (Add (Var ''lowIn'') (Const 1))))"
  by (auto elim: exec.cases)
  with Match
  show "the (σ' ''lowOut'') = the (σ ''lowIn'') + 1"
  by (auto simp: match_def add_opt_def split: option.split)
qed

lemma low_proc_0_sL0:
  "low_proc_02 sL0"
proof (auto simp only: low_proc_02_def)
  fix σ u
  assume Match: "match σ Γ0"
  and "the (σ ''lowIn'') = 0"
  hence LowIn0: "σ ''lowIn'' = Some 0"
  by (cases "σ ''lowIn''", auto simp: match_def)
  from Match
  have "''lowOut''  dom σ"
  by (auto simp: match_def)
  then obtain σ'
  where ExecRand: "Random ''lowOut''  σ  Some σ'"
  and "σ' = σ (''lowOut''  u)"
  by (auto intro: ExecRandomOK)
  hence "the (σ' ''lowOut'') = u"
  by auto
  with ExecRand LowIn0
  show "σ'. sL0  σ  Some σ'  the (σ' ''lowOut'') = u"
  by (metis ExecCondTrue eval.simps(1) eval.simps(2))
qed

lemma low_proc_no_input_change_sL0:
  "low_proc_no_input_change sL0"
proof (unfold low_proc_no_input_change_def, clarify)
  fix σ σ'
  assume "sL0  σ  Some σ'"
  hence "
    Random ''lowOut''  σ  Some σ' 
    Assign ''lowOut'' (Add (Var ''lowIn'') (Const 1))  σ  Some σ'"
  by (auto elim: exec.cases)
  thus "
    the (σ' ''lowIn'') = the (σ ''lowIn'') 
    the (σ' ''highIn'') = the (σ ''highIn'')"
  by (auto elim: exec.cases)
qed

text ‹The refined specification is obtained
by simplification using the definition of @{term sL}.›

definition spec3 :: "prog  bool"
where "spec3 p 
  vars p = vars0 
  (sH.
    body_split p sL0 sH 
    wfs Γ0 sH 
    high_proc2 sH 
    high_proc_no_low_output_change sH)"

lemma step_3_correct:
  "spec3 p  spec2 p"
unfolding spec3_def spec2_def
by (metis
  wfs_sL0 low_proc_non0_sL0 low_proc_0_sL0 low_proc_no_input_change_sL0)

text ‹The non-determinism required by @{const low_proc_0}
cannot be pop-refined away.
In particular, @{term sL} cannot be defined
to copy the high input to the low output when the low input is 0,
which would lead to a program that does not satisfy GNI.›


subsection ‹Step 4›

text ‹\label{sec:refII:stepIV}›

text ‹The processing constraint @{const high_proc2} on @{term sH}
can be satisfied in different ways.
A simple way is to pick the sum of the low and high inputs:
@{const high_proc2} is refined by replacing the inequality with an equality.›

definition high_proc4 :: "stmt  bool"
where "high_proc4 sH 
  σ σ'.
    match σ Γ0 
    sH  σ  Some σ' 
    the (σ' ''highOut'') = the (σ ''lowIn'') + the (σ ''highIn'')"

lemma high_proc4_correct:
  "high_proc4 sH  high_proc2 sH"
by (auto simp: high_proc4_def high_proc2_def)

text ‹The refined specification is obtained
by substituting the refined processing constraint on @{term sH}.›

definition spec4 :: "prog  bool"
where "spec4 p 
  vars p = vars0 
  (sH.
    body_split p sL0 sH 
    wfs Γ0 sH 
    high_proc4 sH 
    high_proc_no_low_output_change sH)"

lemma step_4_correct:
  "spec4 p  spec3 p"
by (auto simp: spec4_def spec3_def high_proc4_correct)


subsection ‹Step 5›

text ‹\label{sec:refII:stepV}›

text ‹The refined processing constraint @{const high_proc4} on @{term sH}
suggest the use of an assignment that
stores the sum of @{term "''lowIn''"} and @{term "''highIn''"}
into @{term "''highOut''"}.›

abbreviation sH0 :: stmt
where "sH0  Assign ''highOut'' (Add (Var ''lowIn'') (Var ''highIn''))"

lemma wfs_sH0:
  "wfs Γ0 sH0"
by auto

lemma high_proc4_sH0:
  "high_proc4 sH0"
proof (auto simp: high_proc4_def)
  fix σ σ'
  assume Match: "match σ Γ0"
  assume "sH0  σ  Some σ'"
  hence "
    σ' = σ (''highOut''  the (eval σ (Add (Var ''lowIn'') (Var ''highIn''))))"
  by (auto elim: exec.cases)
  with Match
  show "the (σ' ''highOut'') = the (σ ''lowIn'') + the (σ ''highIn'')"
  by (auto simp: match_def add_opt_def split: option.split)
qed

lemma high_proc_no_low_output_change_sH0:
  "high_proc_no_low_output_change sH0"
by (auto simp: high_proc_no_low_output_change_def elim: exec.cases)

text ‹The refined specification is obtained
by simplification using the definition of @{term sH}.›

definition spec5 :: "prog  bool"
where "spec5 p  vars p = vars0  body_split p sL0 sH0"

lemma step_5_correct:
  "spec5 p  spec4 p"
unfolding spec5_def spec4_def
by (metis wfs_sH0 high_proc4_sH0 high_proc_no_low_output_change_sH0)


subsection ‹Step 6›

text ‹\label{sec:refII:stepVI}›

text @{const spec5}, which defines the variables and the body,
is refined to characterize a unique program in explicit syntactic form.›

abbreviation p0 :: prog
where "p0  vars = vars0, body = Seq sL0 sH0"

definition spec6 :: "prog  bool"
where "spec6 p  p = p0"

lemma step_6_correct:
  "spec6 p  spec5 p"
by (auto simp: spec6_def spec5_def body_split_def)

text ‹The program satisfies @{const spec0} by construction.
The program witnesses the consistency of the requirements,
i.e.\ the fact that @{const spec0} is not always false.›

lemma p0_sat_spec0:
  "spec0 p0"
by (metis
 step_1_correct
 step_2_correct
 step_3_correct
 step_4_correct
 step_5_correct
 step_6_correct
 spec6_def)

text ‹From @{const p0}, the program text
\begin{verbatim}
  prog {
    vars {
      lowIn
      lowOut
      highIn
      highOut
    }
    body {
      if (lowIn == 0) {
        randomize lowOut;
      } else {
        lowOut = lowIn + 1;
      }
      highOut = lowIn + highIn;
    }
  }
\end{verbatim}
is easily obtained.›


end %invisible