Theory ADS_Construction

(* Author: Andreas Lochbihler, Digital Asset
   Author: Ognjen Maric, Digital Asset *)

theory ADS_Construction imports
  Merkle_Interface
  "HOL-Library.Simps_Case_Conv"
begin

(************************************************************)
section ‹ Building blocks for authenticated data structures on datatypes ›
(************************************************************)

(************************************************************)
subsection ‹ Building Block: Identity Functor ›
(************************************************************)

text ‹If nothing is blindable in a type, then the type itself is the hash and the ADS of itself.›

abbreviation (input) hash_discrete :: "('a, 'a) hash" where "hash_discrete  id"

abbreviation (input) blinding_of_discrete :: "'a blinding_of" where
  "blinding_of_discrete  (=)"

definition merge_discrete :: "'a merge" where
  "merge_discrete x y = (if x = y then Some y else None)"

lemma blinding_of_discrete_hash:
  "blinding_of_discrete  vimage2p hash_discrete hash_discrete (=)"
  by(auto simp add: vimage2p_def)

lemma blinding_of_on_discrete [locale_witness]:
  "blinding_of_on UNIV hash_discrete blinding_of_discrete"
  by(unfold_locales)(simp_all add: OO_eq eq_onp_def blinding_of_discrete_hash)

lemma merge_on_discrete [locale_witness]:
  "merge_on UNIV hash_discrete blinding_of_discrete merge_discrete"
  by unfold_locales(auto simp add: merge_discrete_def)

lemma merkle_discrete [locale_witness]:
  "merkle_interface hash_discrete blinding_of_discrete merge_discrete"
  ..

parametric_constant merge_discrete_parametric [transfer_rule]: merge_discrete_def

(************************************************************)
subsubsection ‹Example: instantiation for @{typ unit}
(************************************************************)

abbreviation (input) hash_unit :: "(unit, unit) hash" where "hash_unit  hash_discrete"

abbreviation blinding_of_unit :: "unit blinding_of" where
  "blinding_of_unit  blinding_of_discrete"

abbreviation merge_unit :: "unit merge" where "merge_unit  merge_discrete"

lemma blinding_of_unit_hash:
  "blinding_of_unit  vimage2p hash_unit hash_unit (=)"
  by(fact blinding_of_discrete_hash)

lemma blinding_of_on_unit:
  "blinding_of_on UNIV hash_unit blinding_of_unit"
  by(fact blinding_of_on_discrete)

lemma merge_on_unit:
  "merge_on UNIV hash_unit blinding_of_unit merge_unit"
  by(fact merge_on_discrete)

lemma merkle_interface_unit:
  "merkle_interface hash_unit blinding_of_unit merge_unit"
  by(intro merkle_interfaceI merge_on_unit)

(************************************************************)
subsection ‹ Building Block: Blindable Position ›
(************************************************************)

type_synonym 'a blindable = 'a

text ‹ The following type represents the hashes of a datatype. We model hashes as being injective,
  but not surjective; some hashes do not correspond to any values of the original datatypes. We
  model such values as "garbage" coming from a countable set (here, naturals). ›

type_synonym garbage = nat

datatype 'ah blindableh = Content 'ah | Garbage garbage

datatype ('am, 'ah) blindablem = Unblinded 'am | Blinded "'ah blindableh"

(************************************************************)
subsubsection ‹ Hashes ›
(************************************************************)

primrec hash_blindable' :: "(('ah, 'ah) blindablem, 'ah blindableh) hash" where
  "hash_blindable' (Unblinded x) = Content x"
| "hash_blindable' (Blinded x) = x"

definition hash_blindable :: "('am, 'ah) hash  (('am, 'ah) blindablem, 'ah blindableh) hash" where
  "hash_blindable h = hash_blindable'  map_blindablem h id"

lemma hash_blindable_simps [simp]:
  "hash_blindable h (Unblinded x) = Content (h x)"
  "hash_blindable h (Blinded y) = y"
  by(simp_all add: hash_blindable_def blindableh.map_id)

lemma hash_map_blindable_simp:
  "hash_blindable f (map_blindablem f' id x) = hash_blindable (f o f') x"
  by(cases x) (simp_all add: hash_blindable_def blindableh.map_comp)

parametric_constant hash_blindable'_parametric [transfer_rule]: hash_blindable'_def

parametric_constant hash_blindable_parametric [transfer_rule]: hash_blindable_def

(************************************************************)
subsubsection ‹ Blinding ›
(************************************************************)

context
  fixes h :: "('am, 'ah) hash"
    and bo :: "'am blinding_of"
begin

inductive blinding_of_blindable :: "('am, 'ah) blindablem blinding_of" where
  "blinding_of_blindable (Unblinded x) (Unblinded y)" if "bo x y"
| "blinding_of_blindable (Blinded x) t" if "hash_blindable h t = x"

inductive_simps blinding_of_blindable_simps [simp]:
  "blinding_of_blindable (Unblinded x) y"
  "blinding_of_blindable (Blinded x) y"
  "blinding_of_blindable z (Unblinded x)"
  "blinding_of_blindable z (Blinded x)"

inductive_simps blinding_of_blindable_simps2:
   "blinding_of_blindable (Unblinded x) (Unblinded y)"
   "blinding_of_blindable (Unblinded x) (Blinded y')"
   "blinding_of_blindable (Blinded x') (Unblinded y)"
   "blinding_of_blindable (Blinded x') (Blinded y')"

end

lemma blinding_of_blindable_mono:
  assumes "bo  bo'"
  shows "blinding_of_blindable h bo  blinding_of_blindable h bo'"
  apply(rule predicate2I)
  apply(erule blinding_of_blindable.cases; hypsubst)
  subgoal by(rule blinding_of_blindable.intros)(rule assms[THEN predicate2D])
  subgoal by(rule blinding_of_blindable.intros) simp
  done

lemma blinding_of_blindable_hash:
  assumes "bo  vimage2p h h (=)"
  shows "blinding_of_blindable h bo  vimage2p (hash_blindable h) (hash_blindable h) (=)"
  apply(rule predicate2I vimage2pI)+
  apply(erule blinding_of_blindable.cases; hypsubst)
  subgoal using assms[THEN predicate2D] by(simp add: vimage2p_def)
  subgoal by simp
  done

lemma blinding_of_on_blindable [locale_witness]:
  assumes "blinding_of_on A h bo"
  shows "blinding_of_on {x. set1_blindablem x  A} (hash_blindable h) (blinding_of_blindable h bo)"
  (is "blinding_of_on ?A ?h ?bo")
proof -
  interpret blinding_of_on A h bo by fact
  show ?thesis
  proof
    show "?bo  vimage2p ?h ?h (=)"
      by(rule blinding_of_blindable_hash)(rule hash)
    show "?bo x x" if "x  ?A" for x using that by(cases x)(auto simp add: refl)
    show "?bo x z" if "?bo x y" "?bo y z" "x  ?A" for x y z using that
      by(auto elim!: blinding_of_blindable.cases dest: trans blinding_hash_eq)
    show "x = y" if "?bo x y" "?bo y x" "x  ?A" for x y using that
      by(auto elim!: blinding_of_blindable.cases dest: antisym)
  qed
qed

lemmas blinding_of_blindable [locale_witness] = blinding_of_on_blindable[of UNIV, simplified]

case_of_simps blinding_of_blindable_alt_def: blinding_of_blindable_simps2
parametric_constant blinding_of_blindable_parametric [transfer_rule]: blinding_of_blindable_alt_def

(************************************************************)
subsubsection ‹ Merging ›
(************************************************************)

context
  fixes h :: "('am, 'ah) hash"
  fixes m :: "'am merge"
begin

fun merge_blindable :: "('am, 'ah) blindablem merge" where
  "merge_blindable (Unblinded x) (Unblinded y) = map_option Unblinded (m x y)"
| "merge_blindable (Blinded x) (Unblinded y) = (if x = Content (h y) then Some (Unblinded y) else None)"
| "merge_blindable (Unblinded y) (Blinded x) = (if x = Content (h y) then Some (Unblinded y) else None)"
| "merge_blindable (Blinded t) (Blinded u) = (if t = u then Some (Blinded u) else None)"

lemma merge_on_blindable [locale_witness]:
  assumes "merge_on A h bo m"
  shows "merge_on {x. set1_blindablem x  A} (hash_blindable h) (blinding_of_blindable h bo) merge_blindable"
  (is "merge_on ?A ?h ?bo ?m")
proof -
  interpret merge_on A h bo m by fact
  show ?thesis
  proof
    show "ab. ?m a b = Some ab  ?bo a ab  ?bo b ab  (u. ?bo a u  ?bo b u  ?bo ab u)" if "?h a = ?h b" "a  ?A" for a b
      using that by(cases "(a, b)" rule: merge_blindable.cases)(auto simp add: refl dest!: join)
    show "?m a b = None" if "?h a  ?h b" "a  ?A" for a b 
      using that by(cases "(a, b)" rule: merge_blindable.cases)(auto simp add: dest!: undefined)
  qed
qed

lemmas merge_blindable [locale_witness] = 
  merge_on_blindable[of UNIV, simplified]

end

lemma merge_blindable_alt_def:
  "merge_blindable h m x y = (case (x, y) of
    (Unblinded x, Unblinded y)  map_option Unblinded (m x y)
  | (Blinded x, Unblinded y)  (if Content (h y) = x then Some (Unblinded y) else None)
  | (Unblinded y, Blinded x)  (if Content (h y) = x then Some (Unblinded y) else None)
  | (Blinded t, Blinded u)  (if t = u then Some (Blinded u) else None))"
  by(simp split: blindablem.split blindableh.split)

parametric_constant merge_blindable_parametric [transfer_rule]: merge_blindable_alt_def

lemma merge_blindable_cong [fundef_cong]:
  assumes "a b.  a  set1_blindablem x; b  set1_blindablem y   m a b = m' a b"
  shows "merge_blindable h m x y = merge_blindable h m' x y"
  by(auto simp add: merge_blindable_alt_def split: blindablem.split intro: assms intro!: arg_cong[where f="map_option _"])

(************************************************************)
subsubsection ‹ Merkle interface ›
(************************************************************)

lemma merkle_blindable [locale_witness]:
  assumes "merkle_interface h bo m"
  shows "merkle_interface (hash_blindable h) (blinding_of_blindable h bo) (merge_blindable h m)"
proof -
  interpret merge_on UNIV h bo m using assms by(simp add: merkle_interface_aux)
  show ?thesis unfolding merkle_interface_aux ..
qed


(************************************************************)
subsubsection ‹ Non-recursive blindable positions ›
(************************************************************)

text ‹ For a non-recursive data type @{typ 'a}, the type of hashes in @{type blindablem} is fixed
to be simply @{typ "'a blindableh"}. We obtain this by instantiating the type variable with the
identity building block. ›

type_synonym 'a nr_blindable = "('a, 'a) blindablem"

abbreviation hash_nr_blindable :: "('a nr_blindable, 'a blindableh) hash" where
  "hash_nr_blindable  hash_blindable hash_discrete"

abbreviation blinding_of_nr_blindable :: "'a nr_blindable blinding_of" where
  "blinding_of_nr_blindable  blinding_of_blindable hash_discrete blinding_of_discrete"

abbreviation merge_nr_blindable :: "'a nr_blindable merge" where
  "merge_nr_blindable  merge_blindable hash_discrete merge_discrete"

lemma merge_on_nr_blindable:
  "merge_on UNIV hash_nr_blindable blinding_of_nr_blindable merge_nr_blindable"
  ..

lemma merkle_nr_blindable:
  "merkle_interface hash_nr_blindable blinding_of_nr_blindable merge_nr_blindable"
  ..

(************************************************************)
subsection ‹ Building block: Sums ›
(************************************************************)

text ‹ We prove that we can lift the ADS construction through sums.›

type_synonym ('ah, 'bh) sumh = "'ah + 'bh"
type_notation sumh (infixr +h 10)

type_synonym ('am, 'bm) summ = "'am + 'bm"
  ― ‹If a functor does not introduce blindable positions, then we don't need the type variable copies.›
type_notation summ (infixr +m 10)

(************************************************************)
subsubsection ‹ Hashes ›
(************************************************************)

abbreviation (input) hash_sum' :: "('ah +h 'bh, 'ah +h 'bh) hash" where
  "hash_sum'  id"

abbreviation (input) hash_sum :: "('am, 'ah) hash  ('bm, 'bh) hash  ('am +m 'bm, 'ah +h 'bh) hash"
  where "hash_sum  map_sum"

(************************************************************)
subsubsection ‹ Blinding ›
(************************************************************)

abbreviation (input) blinding_of_sum :: "'am blinding_of  'bm blinding_of  ('am +m 'bm) blinding_of" where
  "blinding_of_sum  rel_sum"

lemmas blinding_of_sum_mono = sum.rel_mono

lemma blinding_of_sum_hash:
  assumes "boa  vimage2p rha rha (=)" "bob  vimage2p rhb rhb (=)"
  shows "blinding_of_sum boa bob  vimage2p (hash_sum rha rhb) (hash_sum rha rhb) (=)"
  using assms by(auto simp add: vimage2p_def elim!: rel_sum.cases)

lemma blinding_of_on_sum [locale_witness]:
  assumes "blinding_of_on A rha boa" "blinding_of_on B rhb bob"
  shows "blinding_of_on {x. setl x  A  setr x  B} (hash_sum rha rhb) (blinding_of_sum boa bob)"
  (is "blinding_of_on ?A ?h ?bo")
proof -
  interpret a: blinding_of_on A rha boa by fact
  interpret b: blinding_of_on B rhb bob by fact
  show ?thesis
  proof
    show "?bo x x" if "x  ?A" for x using that by(intro sum.rel_refl_strong)(auto intro: a.refl b.refl)
    show "?bo x z" if "?bo x y" "?bo y z" "x  ?A" for x y z
      using that by(auto elim!: rel_sum.cases dest: a.trans b.trans)
    show "x = y" if "?bo x y" "?bo y x" "x  ?A" for x y 
      using that by(auto elim!: rel_sum.cases dest: a.antisym b.antisym)
  qed(rule blinding_of_sum_hash a.hash b.hash)+
qed

lemmas blinding_of_sum [locale_witness] = blinding_of_on_sum[of UNIV _ _ UNIV, simplified]

(************************************************************)
subsubsection ‹ Merging ›
(************************************************************)

context
  fixes ma :: "'am merge"
  fixes mb :: "'bm merge"
begin

fun merge_sum :: "('am +m 'bm) merge" where
  "merge_sum (Inl x) (Inl y) = map_option Inl (ma x y)"
| "merge_sum (Inr x) (Inr y) = map_option Inr (mb x y)"
| "merge_sum _ _ = None"

lemma merge_on_sum [locale_witness]:
  assumes "merge_on A rha boa ma" "merge_on B rhb bob mb"
  shows "merge_on {x. setl x  A  setr x  B} (hash_sum rha rhb) (blinding_of_sum boa bob) merge_sum"
  (is "merge_on ?A ?h ?bo ?m")
proof -
  interpret a: merge_on A rha boa ma by fact
  interpret b: merge_on B rhb bob mb by fact
  show ?thesis
  proof
    show "ab. ?m a b = Some ab  ?bo a ab  ?bo b ab  (u. ?bo a u  ?bo b u  ?bo ab u)"
      if "?h a = ?h b" "a  ?A" for a b using that
      by(cases "(a, b)" rule: merge_sum.cases)(auto dest!: a.join b.join elim!: rel_sum.cases)
    show "?m a b = None" if "?h a  ?h b" "a  ?A" for a b using that
      by(cases "(a, b)" rule: merge_sum.cases)(auto dest!: a.undefined b.undefined)
  qed
qed

lemmas merge_sum [locale_witness] = merge_on_sum[where A=UNIV and B=UNIV, simplified]

lemma merge_sum_alt_def:
  "merge_sum x y = (case (x, y) of
    (Inl x, Inl y)  map_option Inl (ma x y)
  | (Inr x, Inr y)  map_option Inr (mb x y)
  | _  None)"
  by(simp add: split: sum.split)

end

lemma merge_sum_cong[fundef_cong]:
  " x = x'; y = y'; 
    xl yl.  x = Inl xl; y = Inl yl   ma xl yl = ma' xl yl;
    xr yr.  x = Inr xr; y = Inr yr   mb xr yr = mb' xr yr  
    merge_sum ma mb x y = merge_sum ma' mb' x' y'"
  by(cases x; simp_all; cases y; auto)

parametric_constant merge_sum_parametric [transfer_rule]: merge_sum_alt_def

subsubsection ‹ Merkle interface ›

lemma merkle_sum [locale_witness]:
  assumes "merkle_interface rha boa ma" "merkle_interface rhb bob mb"
  shows "merkle_interface (hash_sum rha rhb) (blinding_of_sum boa bob) (merge_sum ma mb)"
proof -
  interpret a: merge_on UNIV rha boa ma unfolding merkle_interface_aux[symmetric] by fact
  interpret b: merge_on UNIV rhb bob mb unfolding merkle_interface_aux[symmetric] by fact
  show ?thesis unfolding merkle_interface_aux[symmetric] ..
qed

(************************************************************)
subsection ‹ Building Block: Products›
(************************************************************)

text ‹ We prove that we can lift the ADS construction through products.›

type_synonym ('ah, 'bh) prodh = "'ah × 'bh"
type_notation prodh ((_ ×h/ _) [21, 20] 20)

type_synonym ('am, 'bm) prodm = "'am × 'bm"
  ― ‹If a functor does not introduce blindable positions, then we don't need the type variable copies.›
type_notation prodm ((_ ×m/ _) [21, 20] 20)

(************************************************************)
subsubsection ‹ Hashes ›
(************************************************************)

abbreviation (input) hash_prod' :: "('ah ×h 'bh, 'ah ×h 'bh) hash" where
  "hash_prod'  id"

abbreviation (input) hash_prod :: "('am, 'ah) hash  ('bm, 'bh) hash  ('am ×m 'bm, 'ah ×h 'bh) hash"
  where "hash_prod  map_prod"

(************************************************************)
subsubsection ‹ Blinding ›
(************************************************************)

abbreviation (input) blinding_of_prod :: "'am blinding_of  'bm blinding_of  ('am ×m 'bm) blinding_of" where
  "blinding_of_prod  rel_prod"

lemmas blinding_of_prod_mono = prod.rel_mono

lemma blinding_of_prod_hash:
  assumes "boa  vimage2p rha rha (=)" "bob  vimage2p rhb rhb (=)"
  shows "blinding_of_prod boa bob  vimage2p (hash_prod rha rhb) (hash_prod rha rhb) (=)"
  using assms by(auto simp add: vimage2p_def)

lemma blinding_of_on_prod [locale_witness]:
  assumes "blinding_of_on A rha boa" "blinding_of_on B rhb bob"
  shows "blinding_of_on {x. fsts x  A  snds x  B} (hash_prod rha rhb) (blinding_of_prod boa bob)"
  (is "blinding_of_on ?A ?h ?bo")
proof -
  interpret a: blinding_of_on A rha boa by fact
  interpret b: blinding_of_on B rhb bob by fact
  show ?thesis
  proof
    show "?bo x x" if "x  ?A" for x using that by(cases x)(auto intro: a.refl b.refl)
    show "?bo x z" if "?bo x y" "?bo y z" "x  ?A" for x y z using that
      by(auto elim!: rel_prod.cases dest: a.trans b.trans)
    show "x = y" if "?bo x y" "?bo y x" "x  ?A" for x y using that
      by(auto elim!: rel_prod.cases dest: a.antisym b.antisym)
  qed(rule blinding_of_prod_hash a.hash b.hash)+
qed

lemmas blinding_of_prod [locale_witness] = blinding_of_on_prod[where A=UNIV and B=UNIV, simplified]

(************************************************************)
subsubsection ‹ Merging ›
(************************************************************)

context
  fixes ma :: "'am merge"
  fixes mb :: "'bm merge"
begin

fun merge_prod :: "('am ×m 'bm) merge" where
  "merge_prod (x, y) (x', y') = Option.bind (ma x x') (λx''. map_option (Pair x'') (mb y y'))"

lemma merge_on_prod [locale_witness]:
  assumes "merge_on A rha boa ma" "merge_on B rhb bob mb"
  shows "merge_on {x. fsts x  A  snds x  B} (hash_prod rha rhb) (blinding_of_prod boa bob) merge_prod"
  (is "merge_on ?A ?h ?bo ?m")
proof -
  interpret a: merge_on A rha boa ma by fact
  interpret b: merge_on B rhb bob mb by fact
  show ?thesis
  proof
    show "ab. ?m a b = Some ab  ?bo a ab  ?bo b ab  (u. ?bo a u  ?bo b u  ?bo ab u)"
      if "?h a = ?h b" "a  ?A" for a b using that
      by(cases "(a, b)" rule: merge_prod.cases)(auto dest!: a.join b.join)
    show "?m a b = None" if "?h a  ?h b" "a  ?A" for a b using that
      by(cases "(a, b)" rule: merge_prod.cases)(auto dest!: a.undefined b.undefined)
  qed
qed

lemmas merge_prod [locale_witness] = merge_on_prod[where A=UNIV and B=UNIV, simplified]

lemma merge_prod_alt_def:
  "merge_prod = (λ(x, y) (x', y'). Option.bind (ma x x') (λx''. map_option (Pair x'') (mb y y')))"
  by(simp add: fun_eq_iff)

end

lemma merge_prod_cong[fundef_cong]:
  assumes "a b.  a  fsts p1; b  fsts p2   ma a b = ma' a b"
    and "a b.  a  snds p1; b  snds p2   mb a b = mb' a b" 
  shows "merge_prod ma mb p1 p2 = merge_prod ma' mb' p1 p2"
  using assms by(cases p1; cases p2) auto

parametric_constant merge_prod_parametric [transfer_rule]: merge_prod_alt_def

(************************************************************)
subsubsection ‹ Merkle Interface ›
(************************************************************)

lemma merkle_product [locale_witness]:
  assumes "merkle_interface rha boa ma" "merkle_interface rhb bob mb"
  shows "merkle_interface (hash_prod rha rhb) (blinding_of_prod boa bob) (merge_prod ma mb)"
proof -
  interpret a: merge_on UNIV rha boa ma unfolding merkle_interface_aux[symmetric] by fact
  interpret b: merge_on UNIV rhb bob mb unfolding merkle_interface_aux[symmetric] by fact
  show ?thesis unfolding merkle_interface_aux[symmetric] ..
qed


(************************************************************)
subsection ‹Building Block: Lists›
(************************************************************)

text ‹The ADS construction on lists is done the easiest through a separate isomorphic datatype
  that has only a single constructor. We hide this construction in a locale. ›

locale list_R1 begin

type_synonym ('a, 'b) list_F = "unit + 'a × 'b"

abbreviation (input) "set_base_Fm  λx. setr x  fsts"
abbreviation (input) "set_rec_Fm  λA. setr A  snds"
abbreviation (input) "map_F  λfb fr. map_sum id (map_prod fb fr)"

datatype 'a list_R1 = list_R1 (unR: "('a, 'a list_R1) list_F")

lemma list_R1_const_into_dest: "list_R1 F = l  F = unR l"
  by auto

declare list_R1.split[split]

lemma list_R1_induct[case_names list_R1]:
  assumes "F.  l'. l'  set_rec_Fm F   P l'   P (list_R1 F)"
  shows "P l"  
  apply(rule list_R1.induct)
  apply(auto intro!: assms)
  done

lemma set_list_R1_eq: 
  "{x. set_base_Fm x  A  set_rec_Fm x  B} =
   {x. setl x  UNIV  setr x  {x. fsts x  A  snds x  B}}"
  by(auto simp add: bind_UNION)

(************************************************************)
subsubsection ‹ The Isomorphism ›
(************************************************************)

primrec (transfer) list_R1_to_list :: "'a list_R1  'a list" where
  "list_R1_to_list (list_R1 l) = (case map_sum id (map_prod id list_R1_to_list) l of Inl ()  [] | Inr (x, xs)  x # xs)"

lemma list_R1_to_list_simps [simp]:
  "list_R1_to_list (list_R1 (Inl ())) = []"
  "list_R1_to_list (list_R1 (Inr (x, xs))) = x # list_R1_to_list xs"
  by(simp_all split: unit.split)

declare list_R1_to_list.simps [simp del]

primrec (transfer) list_to_list_R1 :: "'a list  'a list_R1" where
  "list_to_list_R1 [] = list_R1 (Inl ())"
| "list_to_list_R1 (x#xs) = list_R1 (Inr (x, list_to_list_R1 xs))"

lemma R1_of_list: "list_R1_to_list (list_to_list_R1 x) = x"
  by(induct x) (auto)

lemma list_of_R1: "list_to_list_R1 (list_R1_to_list x) = x"
  apply(induct x)
  subgoal for x
    by(cases x) (auto)
  done

lemma list_R1_def: "type_definition list_to_list_R1 list_R1_to_list UNIV"
  by(unfold_locales)(auto intro: R1_of_list list_of_R1)

setup_lifting list_R1_def

lemma map_list_R1_list_to_list_R1: "map_list_R1 f (list_to_list_R1 xs) = list_to_list_R1 (map f xs)"
  by(induction xs) auto

lemma list_R1_map_trans [transfer_rule]: includes lifting_syntax shows
  "(((=) ===> (=)) ===> pcr_list (=) ===> pcr_list (=)) map_list_R1 map"
  by(auto 4 3 simp add: list.pcr_cr_eq rel_fun_eq cr_list_def map_list_R1_list_to_list_R1)

lemma set_list_R1_list_to_list_R1: "set_list_R1 (list_to_list_R1 xs) = set xs"
  by(induction xs) auto

lemma list_R1_set_trans [transfer_rule]: includes lifting_syntax shows
  "(pcr_list (=) ===> (=)) set_list_R1 set"
  by(auto simp add: list.pcr_cr_eq cr_list_def set_list_R1_list_to_list_R1)

lemma rel_list_R1_list_to_list_R1:
   "rel_list_R1 R (list_to_list_R1 xs) (list_to_list_R1 ys)  list_all2 R xs ys"
  (is "?lhs  ?rhs")
proof
  define xs' and ys' where "xs' = list_to_list_R1 xs" and "ys' = list_to_list_R1 ys"
  assume "rel_list_R1 R xs' ys'"
  then have "list_all2 R (list_R1_to_list xs') (list_R1_to_list ys')"
    by induction(auto elim!: rel_sum.cases)
  thus ?rhs by(simp add: xs'_def ys'_def R1_of_list)
next
  show ?lhs if ?rhs using that by induction auto
qed

lemma list_R1_rel_trans[transfer_rule]: includes lifting_syntax shows
  "(((=) ===> (=) ===> (=)) ===> pcr_list (=) ===> pcr_list (=) ===> (=)) rel_list_R1 list_all2"
  by(auto 4 4 simp add: list.pcr_cr_eq rel_fun_eq cr_list_def rel_list_R1_list_to_list_R1)

(************************************************************)
subsubsection ‹ Hashes ›
(************************************************************)

type_synonym ('ah, 'bh) list_Fh = "unit +h 'ah ×h 'bh"

type_synonym ('am, 'bm) list_Fm = "unit +m 'am ×m 'bm"

type_synonym 'ah list_R1h = "'ah list_R1" 
  ― ‹In theory, we should define a separate datatype here of the functor @{typ "('ah, _) list_Fh"}.
    We take a shortcut because they're isomorphic.›

type_synonym 'am list_R1m = "'am list_R1"
  ― ‹In theory, we should define a separate datatype here of the functor @{typ "('am, _) list_Fm"}.
    We take a shortcut because they're isomorphic.›

definition hash_F :: "('am, 'ah) hash  ('bm, 'bh) hash  (('am, 'bm) list_Fm, ('ah, 'bh) list_Fh) hash" where 
  "hash_F h rhL = hash_sum hash_unit (hash_prod h rhL)"

abbreviation (input) hash_R1 :: "('am, 'ah) hash  ('am list_R1m, 'ah list_R1h) hash" where
  "hash_R1  map_list_R1"

parametric_constant hash_F_parametric[transfer_rule]: hash_F_def

(************************************************************)
subsubsection ‹ Blinding ›
(************************************************************)

definition blinding_of_F :: "'am blinding_of  'bm blinding_of  ('am, 'bm) list_Fm blinding_of" where
  "blinding_of_F bo bL = blinding_of_sum blinding_of_unit (blinding_of_prod bo bL)"
 
abbreviation (input) blinding_of_R1 :: "'a blinding_of  'a list_R1 blinding_of" where
  "blinding_of_R1  rel_list_R1"

lemma blinding_of_hash_R1:
  assumes "bo  vimage2p h h (=)"
  shows "blinding_of_R1 bo  vimage2p (hash_R1 h) (hash_R1 h) (=)"
  apply(rule predicate2I vimage2pI)+
  apply(auto simp add: predicate2D_vimage2p[OF assms] elim!: list_R1.rel_induct rel_sum.cases rel_prod.cases)
  done

lemma blinding_of_on_R1 [locale_witness]:
  assumes "blinding_of_on A h bo"
  shows "blinding_of_on {x. set_list_R1 x  A} (hash_R1 h) (blinding_of_R1 bo)"
  (is "blinding_of_on ?A ?h ?bo")
proof -
  interpret a: blinding_of_on A h bo by fact
  show ?thesis
  proof
    show hash: "?bo  vimage2p ?h ?h (=)" using a.hash by(rule blinding_of_hash_R1)

    have "?bo x x  (?bo x y  ?bo y z  ?bo x z)  (?bo x y  ?bo y x  x = y)" if "x  ?A" for x y z using that
    proof(induction x arbitrary: y z)
      case (list_R1 x y' z')
      from list_R1.prems have s1: "set_base_Fm x  A" by(fastforce)
      from list_R1.prems have s3: "set_rec_Fm x  set_list_R1  A" by(fastforce intro: rev_bexI)
     
      interpret F: blinding_of_on "{y. set_base_Fm y  A  set_rec_Fm y  set_rec_Fm x}"
        "hash_F h (hash_R1 h)" "blinding_of_F bo (blinding_of_R1 bo)"
        unfolding hash_F_def blinding_of_F_def set_list_R1_eq
      proof
        let ?A' = "setr x  snds" and ?bo' = "rel_list_R1 bo"
        show "?bo' x x" if "x  ?A'" for x using that list_R1 by(force simp add: eq_onp_def)
        show "?bo' x z" if "?bo' x y" "?bo' y z" "x  ?A'" for x y z 
          using that list_R1.IH[of _ x y z] list_R1.prems
          by(force simp add: bind_UNION prod_set_defs)
        show "x = y" if "?bo' x y" "?bo' y x" "x  ?A'" for x y
          using that list_R1.IH[of _ x y] list_R1.prems
          by(force simp add: prod_set_defs)
      qed(rule hash)
      show ?case using list_R1.prems
        apply(intro conjI)
        subgoal using F.refl[of x] s1 unfolding blinding_of_F_def by(auto intro: list_R1.rel_intros)
        subgoal using s1 by(auto elim!: list_R1.rel_cases F.trans[unfolded blinding_of_F_def] intro: list_R1.rel_intros)
        subgoal using s1 by(auto elim!: list_R1.rel_cases dest: F.antisym[unfolded blinding_of_F_def])
        done
    qed
    then show "x  ?A  ?bo x x" 
      and " ?bo x y; ?bo y z; x  ?A   ?bo x z"
      and " ?bo x y; ?bo y x; x  ?A   x = y"
      for x y z by blast+
  qed
qed

lemmas blinding_of_R1 [locale_witness] = blinding_of_on_R1[where A=UNIV, simplified]

parametric_constant blinding_of_F_parametric[transfer_rule]: blinding_of_F_def

(************************************************************)
subsubsection ‹ Merging ›
(************************************************************)

definition merge_F :: "'am merge  'bm merge  ('am, 'bm) list_Fm merge" where 
  "merge_F m mL = merge_sum merge_unit (merge_prod m mL)"

lemma merge_F_cong[fundef_cong]:
  assumes "a b.  a  set_base_Fm x; b  set_base_Fm y   m a b = m' a b"
    and "a b.  a  set_rec_Fm x; b  set_rec_Fm y   mL a b = mL' a b"
  shows "merge_F m mL x y = merge_F m' mL' x y"
  using assms
  apply(cases x; cases y)
     apply(simp_all add: merge_F_def)
  apply(rule arg_cong[where f="map_option _"])
  apply(blast intro: merge_prod_cong)
  done

context
  fixes m :: "'am merge" 
  notes setr.simps[simp]
begin
fun merge_R1 :: "'am list_R1m merge" where
  "merge_R1 (list_R1 l1) (list_R1 l2) = map_option list_R1 (merge_F m merge_R1 l1 l2)"
end

case_of_simps merge_cases [simp]: merge_R1.simps

lemma merge_on_R1:
  assumes "merge_on A h bo m"
  shows "merge_on {x. set_list_R1 x  A } (hash_R1 h) (blinding_of_R1 bo) (merge_R1 m)"
  (is "merge_on ?A ?h ?bo ?m")
proof -
  interpret a: merge_on A h bo m by fact
  show ?thesis
  proof
    have "(?h a = ?h b  (ab. ?m a b = Some ab  ?bo a ab  ?bo b ab  (u. ?bo a u  ?bo b u  ?bo ab u))) 
      (?h a  ?h b  ?m a b = None)"
      if "a  ?A" for a b using that unfolding mem_Collect_eq
    proof(induction a arbitrary: b rule: list_R1_induct)
      case wfInd: (list_R1 l)
      interpret merge_on "{y. set_base_Fm y  A  set_rec_Fm y  set_rec_Fm l}"
        "hash_F h ?h" "blinding_of_F bo ?bo" "merge_F m ?m"
        unfolding set_list_R1_eq hash_F_def merge_F_def blinding_of_F_def
      proof
        fix a
        assume a: "a  set_rec_Fm l"
        with wfInd.prems have a': "set_list_R1 a  A"
          by fastforce

        show "hash_R1 h a = hash_R1 h b
            ab. ?m a b = Some ab  ?bo a ab  ?bo b ab 
                    (u. ?bo a u  ?bo b u  ?bo ab u)"
          and "?h a  ?h b  ?m a b = None" for b      
          using wfInd.IH[OF a a', rule_format, of b]
          by(auto dest: sym)
      qed
      show ?case using wfInd.prems
        apply(intro conjI strip)
        subgoal
          by(auto 4 4 dest!: join[unfolded hash_F_def]
              simp add: blinding_of_F_def UN_subset_iff list_R1.rel_sel)
        subgoal by(auto 4 3 intro!: undefined[simplified hash_F_def])
        done
    qed
    then show
      "?h a = ?h b  ab. ?m a b = Some ab  ?bo a ab  ?bo b ab  (u. ?bo a u  ?bo b u  ?bo ab u)"
      "?h a  ?h b  ?m a b = None"
      if "a  ?A" for a b using that by blast+
  qed
qed

lemmas merge_R1 [locale_witness] = merge_on_R1[where A=UNIV, simplified]

lemma merkle_list_R1 [locale_witness]:
  assumes "merkle_interface h bo m"
  shows "merkle_interface (hash_R1 h) (blinding_of_R1 bo) (merge_R1 m)"
proof -
  interpret merge_on UNIV h bo m using assms by(unfold merkle_interface_aux)
  show ?thesis unfolding merkle_interface_aux[symmetric] ..
qed

lemma merge_R1_cong [fundef_cong]:
  assumes "a b.  a  set_list_R1 x; b  set_list_R1 y   m a b = m' a b"
  shows "merge_R1 m x y = merge_R1 m' x y"
  using assms
  apply(induction x y rule: merge_R1.induct)
  apply(simp del: merge_cases)
  apply(rule arg_cong[where f="map_option _"])
  apply(blast intro: merge_F_cong[unfolded bind_UNION])
  done

parametric_constant merge_F_parametric[transfer_rule]: merge_F_def

lemma merge_R1_parametric [transfer_rule]:
  includes lifting_syntax 
  notes [simp del] = merge_cases
  assumes [transfer_rule]: "bi_unique A"
  shows "((A ===> A ===> rel_option A) ===> rel_list_R1 A ===> rel_list_R1 A ===> rel_option (rel_list_R1 A))
   merge_R1 merge_R1"
  apply(intro rel_funI)
  subgoal premises prems [transfer_rule] for m1 m2 xs1 xs2 ys1 ys2 using prems(2, 3)
    apply(induction xs1 ys1 arbitrary: xs2 ys2 rule: merge_R1.induct)
    apply(elim list_R1.rel_cases rel_sum.cases; clarsimp simp add: option.rel_map merge_F_def merge_discrete_def)
    apply(elim meta_allE; (erule meta_impE, simp)+)
    subgoal premises [transfer_rule] by transfer_prover
    done
  done

end

subsubsection ‹ Transferring the Constructions to Lists ›
type_synonym 'ah listh = "'ah list"
type_synonym 'am listm = "'am list"

context begin
interpretation list_R1 .

abbreviation (input) hash_list :: "('am, 'ah) hash  ('am listm, 'ah listh) hash"
  where "hash_list  map"
abbreviation (input) blinding_of_list :: "'am blinding_of  'am listm blinding_of"
  where "blinding_of_list  list_all2"
lift_definition merge_list :: "'am merge  'am listm merge" is merge_R1 .

lemma blinding_of_list_mono:
  " x y. bo x y  bo' x y   
  blinding_of_list bo x y  blinding_of_list bo' x y"
  by (transfer) (blast intro: list_R1.rel_mono_strong)

lemmas blinding_of_list_hash = blinding_of_hash_R1[Transfer.transferred]
  and blinding_of_on_list [locale_witness] = blinding_of_on_R1[Transfer.transferred]
  and blinding_of_list [locale_witness] = blinding_of_R1[Transfer.transferred]
  and merge_on_list [locale_witness] = merge_on_R1[Transfer.transferred]
  and merge_list [locale_witness] = merge_R1[Transfer.transferred]
  and merge_list_cong = merge_R1_cong[Transfer.transferred]

lemma blinding_of_list_mono_pred:
  "R  R'  blinding_of_list R  blinding_of_list R'"
  by(transfer) (rule list_R1.rel_mono)

lemma blinding_of_list_simp: "blinding_of_list = list_all2"
  by(transfer) (rule refl)

lemma merkle_list [locale_witness]:
  assumes [locale_witness]: "merkle_interface h bo m"
  shows "merkle_interface (hash_list h) (blinding_of_list bo) (merge_list m)"
  by(transfer fixing: h bo m) unfold_locales

parametric_constant merge_list_parametric [transfer_rule]: merge_list_def

lifting_update list.lifting
lifting_forget list.lifting

end


(************************************************************)
subsection ‹Building block: function space›
(************************************************************)

text ‹ We prove that we can lift the ADS construction through functions.›

type_synonym ('a, 'bh) funh = "'a  'bh"
type_notation funh (infixr h 0)

type_synonym ('a, 'bm) funm = "'a  'bm"
type_notation funm (infixr m 0)

(************************************************************)
subsubsection ‹ Hashes ›
(************************************************************)

text ‹ Only the range is live, the domain is dead like for BNFs. ›

abbreviation (input) hash_fun' :: "('a m 'bh, 'a h 'bh) hash" where
  "hash_fun'  id"

abbreviation (input) hash_fun :: "('bm, 'bh) hash  ('a m 'bm, 'a h 'bh) hash"
  where "hash_fun  comp"

(************************************************************)
subsubsection ‹ Blinding ›
(************************************************************)

abbreviation (input) blinding_of_fun :: "'bm blinding_of  ('a m 'bm) blinding_of" where
  "blinding_of_fun  rel_fun (=)"

lemmas blinding_of_fun_mono = fun.rel_mono

lemma blinding_of_fun_hash:
  assumes "bo  vimage2p rh rh (=)"
  shows "blinding_of_fun bo  vimage2p (hash_fun rh) (hash_fun rh) (=)"
  using assms by(auto simp add: vimage2p_def rel_fun_def le_fun_def)

lemma blinding_of_on_fun [locale_witness]:
  assumes "blinding_of_on A rh bo"
  shows "blinding_of_on {x. range x  A} (hash_fun rh) (blinding_of_fun bo)"
  (is "blinding_of_on ?A ?h ?bo")
proof -
  interpret a: blinding_of_on A rh bo by fact
  show ?thesis
  proof
    show "?bo x x" if "x  ?A" for x using that by(auto simp add: rel_fun_def intro: a.refl)
    show "?bo x z" if "?bo x y" "?bo y z" "x  ?A" for x y z using that
      by(auto 4 3 simp add: rel_fun_def intro: a.trans)
    show "x = y" if "?bo x y" "?bo y x" "x  ?A" for x y using that
      by(fastforce simp add: fun_eq_iff rel_fun_def intro: a.antisym)
  qed(rule blinding_of_fun_hash a.hash)+
qed

lemmas blinding_of_fun [locale_witness] = blinding_of_on_fun[where A=UNIV, simplified]

(************************************************************)
subsubsection ‹ Merging ›
(************************************************************)

context
  fixes m :: "'bm merge"
begin

definition merge_fun :: "('a m 'bm) merge" where
  "merge_fun f g = (if x. m (f x) (g x)  None then Some (λx. the (m (f x) (g x))) else None)"

lemma merge_on_fun [locale_witness]:
  assumes "merge_on A rh bo m"
  shows "merge_on {x. range x  A} (hash_fun rh) (blinding_of_fun bo) merge_fun"
  (is "merge_on ?A ?h ?bo ?m")
proof -
  interpret a: merge_on A rh bo m by fact
  show ?thesis
  proof
    show "ab. ?m a b = Some ab  ?bo a ab  ?bo b ab  (u. ?bo a u  ?bo b u  ?bo ab u)"
      if "?h a = ?h b" "a  ?A" for a b 
      using that(1)[THEN fun_cong, unfolded o_apply, THEN a.join, OF that(2)[unfolded mem_Collect_eq, THEN subsetD, OF rangeI]]
      by atomize(subst (asm) choice_iff; auto simp add: merge_fun_def rel_fun_def)
    show "?m a b = None" if "?h a  ?h b" "a  ?A" for a b using that
      by(auto simp add: merge_fun_def fun_eq_iff dest: a.undefined)
  qed
qed

lemmas merge_fun [locale_witness] = merge_on_fun[where A=UNIV, simplified]

end

lemma merge_fun_cong[fundef_cong]:
  assumes "a b.  a  range f; b  range g   m a b = m' a b"
  shows "merge_fun m f g = merge_fun m' f g"
  using assms[OF rangeI rangeI] by(clarsimp simp add: merge_fun_def)

lemma is_none_alt_def: "Option.is_none x  (case x of None  True | Some _  False)"
  by(auto simp add: Option.is_none_def split: option.splits)

parametric_constant is_none_parametric [transfer_rule]: is_none_alt_def

lemma merge_fun_parametric [transfer_rule]: includes lifting_syntax shows
  "((A ===> B ===> rel_option C) ===> ((=) ===> A) ===> ((=) ===> B) ===> rel_option ((=) ===> C))
   merge_fun merge_fun"
proof(intro rel_funI)
  fix m :: "'a merge" and m' :: "'b merge" and f :: "'c  'a" and f' :: "'c  'b" and g :: "'c  'a" and g' :: "'c  'b"
  assume m: "(A ===> B ===> rel_option C) m m'"
    and f: "((=) ===> A) f f'" and g: "((=) ===> B) g g'"
  note [transfer_rule] = this
  have cond [unfolded Option.is_none_def]: "(x. ¬ Option.is_none (m (f x) (g x)))  (x. ¬ Option.is_none (m' (f' x) (g' x)))" 
    by transfer_prover
  moreover 
  have "((=) ===> C) (λx. the (m (f x) (g x))) (λx. the (m' (f' x) (g' x)))" if *: "x. ¬ m (f x) (g x) = None"
  proof -
    obtain fg fg' where m: "m (f x) (g x) = Some (fg x)" and m': "m' (f' x) (g' x) = Some (fg' x)" for x
      using * *[simplified cond]
      by(simp)(subst (asm) (1 2) choice_iff; clarsimp)
    have "rel_option C (Some (fg x)) (Some (fg' x))" for x unfolding m[symmetric] m'[symmetric] by transfer_prover
    then show ?thesis by(simp add: rel_fun_def m m')
  qed
  ultimately show "rel_option ((=) ===> C) (merge_fun m f g) (merge_fun m' f' g')"
    unfolding merge_fun_def by(simp)
qed

(************************************************************)
subsubsection ‹ Merkle Interface ›
(************************************************************)

lemma merkle_fun [locale_witness]:
  assumes "merkle_interface rh bo m"
  shows "merkle_interface (hash_fun rh) (blinding_of_fun bo) (merge_fun m)"
proof -
  interpret a: merge_on UNIV rh bo m unfolding merkle_interface_aux[symmetric] by fact
  show ?thesis unfolding merkle_interface_aux[symmetric] ..
qed

(************************************************************)
subsection ‹Rose trees›
(************************************************************)

text ‹ 
We now define an ADS over rose trees, which is like a arbitrarily branching Merkle tree where each
node in the tree can be blinded, including the root. The number of children and the position of a
child among its siblings cannot be hidden. The construction allows to plug in further blindable
positions in the labels of the nodes.
›

type_synonym ('a, 'b) rose_tree_F = "'a × 'b list"

abbreviation (input) map_rose_tree_F where
  "map_rose_tree_F f1 f2  map_prod f1 (map f2)"
definition map_rose_tree_F_const where
  "map_rose_tree_F_const f1 f2  map_rose_tree_F f1 f2"

datatype 'a rose_tree = Tree "('a, 'a rose_tree) rose_tree_F"

type_synonym ('ah, 'bh) rose_tree_Fh = "('ah ×h 'bh listh) blindableh"

datatype 'ah rose_treeh = Treeh "('ah, 'ah rose_treeh) rose_tree_Fh"

type_synonym ('am, 'ah, 'bm, 'bh) rose_tree_Fm = "('am ×m 'bm listm, 'ah ×h 'bh listh) blindablem"

datatype ('am, 'ah) rose_treem = Treem "('am, 'ah, ('am, 'ah) rose_treem, 'ah rose_treeh) rose_tree_Fm"

abbreviation (input) map_rose_tree_Fm
  :: "('ma  'a)  ('mr  'r)  ('ma, 'ha, 'mr, 'hr) rose_tree_Fm  ('a, 'ha, 'r, 'hr) rose_tree_Fm"
  where
  "map_rose_tree_Fm f g  map_blindablem (map_prod f (map g)) id"

(************************************************************)
subsubsection ‹ Hashes ›
(************************************************************)

abbreviation (input) hash_rt_F' 
  :: "(('ah, 'ah, 'bh, 'bh) rose_tree_Fm, ('ah, 'bh) rose_tree_Fh) hash"
  where
  "hash_rt_F'  hash_blindable id"

definition hash_rt_Fm
  :: "('am, 'ah) hash  ('bm, 'bh) hash  
    (('am, 'ah, 'bm, 'bh) rose_tree_Fm, ('ah, 'bh) rose_tree_Fh) hash" where
  "hash_rt_Fm h rhm  hash_rt_F' o map_rose_tree_Fm h rhm"

lemma hash_rt_Fm_alt_def: "hash_rt_Fm h rhm = hash_blindable (map_prod h (map rhm))"
  by(simp add: hash_rt_Fm_def fun_eq_iff hash_map_blindable_simp)

primrec (transfer) hash_rt_tree'
  :: "(('ah, 'ah) rose_treem, 'ah rose_treeh) hash" where
  "hash_rt_tree' (Treem x) = Treeh (hash_rt_F' (map_rose_tree_Fm id hash_rt_tree' x))"

definition hash_tree
  :: "('am, 'ah) hash  (('am, 'ah) rose_treem, 'ah rose_treeh) hash" where
  "hash_tree h = hash_rt_tree' o map_rose_treem h id"

lemma blindablem_map_compositionality:
  "map_blindablem f g o map_blindablem f' g' = map_blindablem (f o f') (g o g')"
  by(rule ext) (simp add: blindablem.map_comp)

lemma hash_tree_simps [simp]:
  "hash_tree h (Treem x) = Treeh (hash_rt_Fm h (hash_tree h) x)"
  by(simp add: hash_tree_def hash_rt_Fm_def 
      map_prod.comp map_sum.comp rose_treeh.map_comp blindablem.map_comp
      prod.map_id0 rose_treeh.map_id0)

parametric_constant hash_rt_Fm_parametric [transfer_rule]: hash_rt_Fm_alt_def

parametric_constant hash_tree_parametric [transfer_rule]: hash_tree_def

(************************************************************)
subsubsection ‹ Blinding ›
(************************************************************)

abbreviation (input) blinding_of_rt_Fm
  :: "('am, 'ah) hash  'am blinding_of  ('bm, 'bh) hash  'bm blinding_of
       ('am, 'ah, 'bm, 'bh) rose_tree_Fm blinding_of" where
  "blinding_of_rt_Fm ha boa hb bob  blinding_of_blindable (hash_prod ha (map hb))
    (blinding_of_prod boa (blinding_of_list bob))"

lemma blinding_of_rt_Fm_mono:
  " boa  boa'; bob  bob'   blinding_of_rt_Fm ha boa hb bob  blinding_of_rt_Fm ha boa' hb bob'"
  by(intro blinding_of_blindable_mono prod.rel_mono list.rel_mono)

lemma blinding_of_rt_Fm_mono_inductive:
  assumes "x y. boa x y  boa' x y" "x y. bob x y  bob' x y"
  shows "blinding_of_rt_Fm ha boa hb bob x y  blinding_of_rt_Fm ha boa' hb bob' x y"
  apply(rule impI)
  apply(erule blinding_of_rt_Fm_mono[THEN predicate2D, rotated -1])
  using assms by blast+

context
  fixes h :: "('am, 'ah) hash" 
    and bo :: "'am blinding_of"
begin

inductive blinding_of_tree :: "('am, 'ah) rose_treem blinding_of" where
  "blinding_of_tree (Treem t1) (Treem t2)" 
  if "blinding_of_rt_Fm h bo (hash_tree h) blinding_of_tree t1 t2"
monos blinding_of_rt_Fm_mono_inductive

end

inductive_simps blinding_of_tree_simps [simp]:
  "blinding_of_tree h bo (Treem t1) (Treem t2)"

lemma blinding_of_rt_Fm_hash:
  assumes "boa  vimage2p ha ha (=)" "bob  vimage2p hb hb (=)"
  shows "blinding_of_rt_Fm ha boa hb bob  vimage2p (hash_rt_Fm ha hb) (hash_rt_Fm ha hb) (=)"
  apply(rule order_trans)
   apply(rule blinding_of_blindable_hash)
   apply(fold relator_eq)
   apply(unfold vimage2p_map_rel_prod vimage2p_map_list_all2)
   apply(rule prod.rel_mono assms list.rel_mono)+
  apply(simp only: hash_rt_Fm_def vimage2p_comp o_apply hash_blindable_def blindablem.map_id0 id_def[symmetric] vimage2p_id id_apply)
  done

lemma blinding_of_tree_hash:
  assumes "bo  vimage2p h h (=)"
  shows "blinding_of_tree h bo  vimage2p (hash_tree h) (hash_tree h) (=)"
  apply(rule predicate2I vimage2pI)+
  apply(erule blinding_of_tree.induct)
  apply(simp)
  apply(erule blinding_of_rt_Fm_hash[OF assms, THEN predicate2D_vimage2p, rotated 1])
  apply(blast intro: vimage2pI)
  done

abbreviation (input) set1_rt_Fm :: "('am, 'ah, 'bh, 'bm) rose_tree_Fm  'am set" where
  "set1_rt_Fm x  set1_blindablem x  fsts"

abbreviation (input) set3_rt_Fm :: "('am, 'ah, 'bm, 'bh) rose_tree_Fm  'bm set" where 
  "set3_rt_Fm x  (set1_blindablem x  snds)  set"

lemma set_rt_Fm_eq: 
  "{x. set1_rt_Fm x  A  set3_rt_Fm x  B} = 
   {x. set1_blindablem x  {x. fsts x  A  snds x  {x. set x  B}}}"
  by force

lemma hash_blindable_map: "hash_blindable f  map_blindablem g id = hash_blindable (f  g)" 
  by(rule ext) (simp add: hash_blindable_def blindablem.map_comp)

lemma blinding_of_on_tree [locale_witness]:
  assumes "blinding_of_on A h bo"
  shows "blinding_of_on {x. set1_rose_treem x  A} (hash_tree h) (blinding_of_tree h bo)"
  (is "blinding_of_on ?A ?h ?bo")
proof -
  interpret a: blinding_of_on A h bo by fact
  show ?thesis
  proof
    show "?bo  vimage2p ?h ?h (=)" using a.hash by(rule blinding_of_tree_hash)
    have "?bo x x  (?bo x y  ?bo y z  ?bo x z)  (?bo x y  ?bo y x  x = y)" if "x  ?A" for x y z using that
    proof(induction x arbitrary: y z)
      case (Treem x)
      have [locale_witness]: "blinding_of_on (set3_rt_Fm x) (hash_tree h) (blinding_of_tree h bo)"
        apply unfold_locales
        subgoal by(rule blinding_of_tree_hash)(rule a.hash)
        subgoal using Treem.IH Treem.prems by(fastforce simp add: eq_onp_def)
        subgoal for x y z using Treem.IH[of _ _ x y z] Treem.prems by fastforce
        subgoal for x y using Treem.IH[of _ _ x y] Treem.prems by fastforce
        done
      interpret blinding_of_on
        "{a. set1_rt_Fm a  A  set3_rt_Fm a  set3_rt_Fm x}"
        "hash_rt_Fm h ?h" "blinding_of_rt_Fm h bo ?h ?bo" 
        unfolding set_rt_Fm_eq hash_rt_Fm_alt_def ..
      from Treem.prems show ?case
        apply(intro conjI)
        subgoal by(fastforce intro!: blinding_of_tree.intros refl[unfolded hash_rt_Fm_alt_def])
        subgoal by(fastforce elim!: blinding_of_tree.cases trans[unfolded hash_rt_Fm_alt_def] 
                    intro!: blinding_of_tree.intros)
        subgoal by(fastforce elim!: blinding_of_tree.cases antisym[unfolded hash_rt_Fm_alt_def])
        done
    qed
    then show "x  ?A  ?bo x x"
      and " ?bo x y; ?bo y z; x  ?A   ?bo x z"
      and " ?bo x y; ?bo y x; x  ?A   x = y"
      for x y z by blast+
  qed
qed

lemmas blinding_of_tree [locale_witness] = blinding_of_on_tree[where A=UNIV, simplified]

lemma blinding_of_tree_mono:
  "bo  bo'  blinding_of_tree h bo  blinding_of_tree h bo'"
  apply(rule predicate2I)
  apply(erule blinding_of_tree.induct)
  apply(rule blinding_of_tree.intros)
  apply(erule blinding_of_rt_Fm_mono[THEN predicate2D, rotated -1])
  apply(blast)+
  done

(************************************************************)
subsubsection ‹ Merging ›
(************************************************************)

definition merge_rt_Fm 
  :: "('am, 'ah) hash  'am merge  ('bm, 'bh) hash  'bm merge 
      ('am, 'ah, 'bm, 'bh) rose_tree_Fm merge"
  where
  "merge_rt_Fm ha ma hr mr  merge_blindable (hash_prod ha (hash_list hr)) (merge_prod ma (merge_list mr))"

lemma merge_rt_Fm_cong [fundef_cong]:
  assumes "a b.  a  set1_rt_Fm x; b  set1_rt_Fm y   ma a b = ma' a b"
    and "a b.  a  set3_rt_Fm x; b  set3_rt_Fm y   mm a b = mm' a b"
  shows "merge_rt_Fm ha ma hm mm x y = merge_rt_Fm ha ma' hm mm' x y"
  using assms
  apply(cases x; cases y; simp add: merge_rt_Fm_def bind_UNION)
  apply(rule arg_cong[where f="map_option _"])
  apply(blast intro: merge_prod_cong merge_list_cong)
  done

lemma in_set1_blindablem_iff: "x  set1_blindablem y  y = Unblinded x"
  by(cases y) auto

context 
  fixes h :: "('am, 'ah) hash"
    and ma :: "'am merge"
  notes in_set1_blindablem_iff[simp]
begin
fun merge_tree :: "('am, 'ah) rose_treem merge" where
  "merge_tree (Treem x) (Treem y) = map_option Treem (
    merge_rt_Fm h ma (hash_tree h) merge_tree x y)"
end

lemma merge_on_tree [locale_witness]:
  assumes "merge_on A h bo m"
  shows "merge_on {x. set1_rose_treem x  A} (hash_tree h) (blinding_of_tree h bo) (merge_tree h m)"
  (is "merge_on ?A ?h ?bo ?m")
proof -
  interpret a: merge_on A h bo m by fact
  show ?thesis 
  proof
    have "(?h a = ?h b  (ab. ?m a b = Some ab  ?bo a ab  ?bo b ab  (u. ?bo a u  ?bo b u  ?bo ab u))) 
      (?h a  ?h b  ?m a b = None)"
      if "a  ?A" for a b using that unfolding mem_Collect_eq
    proof(induction a arbitrary: b rule: rose_treem.induct)
      case (Treem x y)
      interpret merge_on 
        "{y. set1_rt_Fm y  A  set3_rt_Fm y  set3_rt_Fm x}"
        "hash_rt_Fm h ?h"
        "blinding_of_rt_Fm h bo ?h ?bo"
        "merge_rt_Fm h m ?h ?m"
        unfolding set_rt_Fm_eq hash_rt_Fm_alt_def merge_rt_Fm_def
      proof
        fix a
        assume a: "a  set3_rt_Fm x"
        with Treem.prems have a': "set1_rose_treem a  A"
          by(force simp add: bind_UNION)

        from a obtain l and ab where a'': "ab  set1_blindablem x" "l  snds ab" "a  set l" 
          by(clarsimp simp add: bind_UNION)

        fix b
        from Treem.IH[OF a'' a', rule_format, of b]
        show "hash_tree h a = hash_tree h b
            ab. merge_tree h m a b = Some ab  blinding_of_tree h bo a ab  blinding_of_tree h bo b ab 
                    (u. blinding_of_tree h bo a u  blinding_of_tree h bo b u  blinding_of_tree h bo ab u)"
          and "hash_tree h a  hash_tree h b  merge_tree h m a b = None"
          by(auto dest: sym)
      qed
      show ?case using Treem.prems
        apply(intro conjI strip)
        subgoal by(cases y)(fastforce dest!: join simp add: blinding_of_tree.simps)
        subgoal by (cases y) (fastforce dest!: undefined)
        done
    qed
    then show
      "?h a = ?h b  ab. ?m a b = Some ab  ?bo a ab  ?bo b ab  (u. ?bo a u  ?bo b u  ?bo ab u)"
      "?h a  ?h b  ?m a b = None"
      if "a  ?A" for a b using that by blast+
  qed
qed

lemmas merge_tree [locale_witness] = merge_on_tree[where A=UNIV, simplified]

lemma option_bind_comm:
 "((x :: 'a option)  (λy. c  (λz. f y z))) = (c  (λy. x  (λz. f z y)))"
  by(cases x; cases c; auto)

parametric_constant merge_rt_Fm_parametric [transfer_rule]: merge_rt_Fm_def

(************************************************************) 
subsubsection ‹Merkle interface›
(************************************************************)

lemma merkle_tree [locale_witness]:
  assumes "merkle_interface h bo m"
  shows "merkle_interface (hash_tree h) (blinding_of_tree h bo) (merge_tree h m)"
proof -
  interpret merge_on UNIV h bo m using assms unfolding merkle_interface_aux .
  show ?thesis unfolding merkle_interface_aux[symmetric] ..
qed

lemma merge_tree_cong [fundef_cong]:
  assumes "a b.  a  set1_rose_treem x; b  set1_rose_treem y   m a b = m' a b"
  shows "merge_tree h m x y = merge_tree h m' x y"
  using assms
  apply(induction x y rule: merge_tree.induct)
  apply(simp add: bind_UNION)
  apply(rule arg_cong[where f="map_option _"])
  apply(rule merge_rt_Fm_cong; simp add: bind_UNION; blast)
  done

end