diff --git a/_CoqProject b/_CoqProject
index 2c69aa50d476fa7efe13c3a39f94f0e913d3e38e..491ead672f6a8d695f39bece2b8b621d5a37fb73 100644
--- a/_CoqProject
+++ b/_CoqProject
@@ -41,4 +41,5 @@ theories/hlist.v
 theories/sorting.v
 theories/infinite.v
 theories/nat_cancel.v
+theories/namespaces.v
 
diff --git a/theories/namespaces.v b/theories/namespaces.v
new file mode 100644
index 0000000000000000000000000000000000000000..677dfb51d432f827638457d6dd0596ad71be8dc7
--- /dev/null
+++ b/theories/namespaces.v
@@ -0,0 +1,105 @@
+From stdpp Require Export countable coPset.
+Set Default Proof Using "Type".
+
+Definition namespace := list positive.
+Instance namespace_eq_dec : EqDecision namespace := _.
+Instance namespace_countable : Countable namespace := _.
+Typeclasses Opaque namespace.
+
+Definition nroot : namespace := nil.
+
+Definition ndot_def `{Countable A} (N : namespace) (x : A) : namespace :=
+  encode x :: N.
+Definition ndot_aux : seal (@ndot_def). by eexists. Qed.
+Definition ndot {A A_dec A_count}:= unseal ndot_aux A A_dec A_count.
+Definition ndot_eq : @ndot = @ndot_def := seal_eq ndot_aux.
+
+Definition nclose_def (N : namespace) : coPset := coPset_suffixes (encode N).
+Definition nclose_aux : seal (@nclose_def). by eexists. Qed.
+Instance nclose : UpClose namespace coPset := unseal nclose_aux.
+Definition nclose_eq : @nclose = @nclose_def := seal_eq nclose_aux.
+
+Notation "N .@ x" := (ndot N x)
+  (at level 19, left associativity, format "N .@ x") : stdpp_scope.
+Notation "(.@)" := ndot (only parsing) : stdpp_scope.
+
+Instance ndisjoint : Disjoint namespace := λ N1 N2, nclose N1 ## nclose N2.
+
+Section namespace.
+  Context `{Countable A}.
+  Implicit Types x y : A.
+  Implicit Types N : namespace.
+  Implicit Types E : coPset.
+
+  Global Instance ndot_inj : Inj2 (=) (=) (=) (@ndot A _ _).
+  Proof. intros N1 x1 N2 x2; rewrite !ndot_eq; naive_solver. Qed.
+
+  Lemma nclose_nroot : ↑nroot = (⊤:coPset).
+  Proof. rewrite nclose_eq. by apply (sig_eq_pi _). Qed.
+  Lemma encode_nclose N : encode N ∈ (↑N:coPset).
+  Proof.
+    rewrite nclose_eq.
+    by apply elem_coPset_suffixes; exists xH; rewrite (left_id_L _ _).
+  Qed.
+
+  Lemma nclose_subseteq N x : ↑N.@x ⊆ (↑N : coPset).
+  Proof.
+    intros p. unfold up_close. rewrite !nclose_eq, !ndot_eq.
+    unfold nclose_def, ndot_def; rewrite !elem_coPset_suffixes.
+    intros [q ->]. destruct (list_encode_suffix N (ndot_def N x)) as [q' ?].
+    { by exists [encode x]. }
+    by exists (q ++ q')%positive; rewrite <-(assoc_L _); f_equal.
+  Qed.
+
+  Lemma nclose_subseteq' E N x : ↑N ⊆ E → ↑N.@x ⊆ E.
+  Proof. intros. etrans; eauto using nclose_subseteq. Qed.
+
+  Lemma ndot_nclose N x : encode (N.@x) ∈ (↑N:coPset).
+  Proof. apply nclose_subseteq with x, encode_nclose. Qed.
+  Lemma nclose_infinite N : ¬set_finite (↑ N : coPset).
+  Proof. rewrite nclose_eq. apply coPset_suffixes_infinite. Qed.
+
+  Lemma ndot_ne_disjoint N x y : x ≠ y → N.@x ## N.@y.
+  Proof.
+    intros Hxy a. unfold up_close. rewrite !nclose_eq, !ndot_eq.
+    unfold nclose_def, ndot_def; rewrite !elem_coPset_suffixes.
+    intros [qx ->] [qy Hqy].
+    revert Hqy. by intros [= ?%encode_inj]%list_encode_suffix_eq.
+  Qed.
+
+  Lemma ndot_preserve_disjoint_l N E x : ↑N ## E → ↑N.@x ## E.
+  Proof. intros. pose proof (nclose_subseteq N x). set_solver. Qed.
+
+  Lemma ndot_preserve_disjoint_r N E x : E ## ↑N → E ## ↑N.@x.
+  Proof. intros. by apply symmetry, ndot_preserve_disjoint_l. Qed.
+
+  Lemma ndisj_subseteq_difference N E F : E ## ↑N → E ⊆ F → E ⊆ F ∖ ↑N.
+  Proof. set_solver. Qed.
+
+  Lemma namespace_subseteq_difference_l E1 E2 E3 : E1 ⊆ E3 → E1 ∖ E2 ⊆ E3.
+  Proof. set_solver. Qed.
+
+  Lemma ndisj_difference_l E N1 N2 : ↑N2 ⊆ (↑N1 : coPset) → E ∖ ↑N1 ## ↑N2.
+  Proof. set_solver. Qed.
+End namespace.
+
+(* The hope is that registering these will suffice to solve most goals
+of the forms:
+- [N1 ## N2]
+- [↑N1 ⊆ E ∖ ↑N2 ∖ .. ∖ ↑Nn]
+- [E1 ∖ ↑N1 ⊆ E2 ∖ ↑N2 ∖ .. ∖ ↑Nn] *)
+Create HintDb ndisj.
+Hint Resolve ndisj_subseteq_difference : ndisj.
+Hint Extern 0 (_ ## _) => apply ndot_ne_disjoint; congruence : ndisj.
+Hint Resolve ndot_preserve_disjoint_l ndot_preserve_disjoint_r : ndisj.
+Hint Resolve nclose_subseteq' ndisj_difference_l : ndisj.
+Hint Resolve namespace_subseteq_difference_l | 100 : ndisj.
+Hint Resolve (empty_subseteq (A:=positive) (C:=coPset)) : ndisj.
+Hint Resolve (union_least (A:=positive) (C:=coPset)) : ndisj.
+
+Ltac solve_ndisj :=
+  repeat match goal with
+  | H : _ ∪ _ ⊆ _ |- _ => apply union_subseteq in H as [??]
+  end;
+  solve [eauto with ndisj].
+Hint Extern 1000 => solve_ndisj : solve_ndisj.