diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0881670fda22fcdc6cbed745d056ef3dfd0c5f71..29542846fee0070e85dd42ab60a03df4fea20559 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,9 @@ API-breaking change is listed.
 
 ## std++ master
 
+- Added `rotate` and `rotate_take` functions for accessing a list with
+  wrap-around. Also added `rotate_nat_add` and `rotate_nat_sub` for
+  computing indicies into a rotated list.
 - Added the `select` and `revert select` tactics for selecting and
   reverting a hypothesis based on a pattern.
 - Extracted `list_numbers.v` from `list.v` and `numbers.v` for
diff --git a/theories/list.v b/theories/list.v
index bf29b3cec5dee06ad1684fdefd8f84d004c1cd6d..0dfd1471c0cd7e3bce906d80daccbc3ba359da4a 100644
--- a/theories/list.v
+++ b/theories/list.v
@@ -143,6 +143,20 @@ Fixpoint replicate {A} (n : nat) (x : A) : list A :=
   match n with 0 => [] | S n => x :: replicate n x end.
 Instance: Params (@replicate) 2 := {}.
 
+(** The function [rotate n l] rotates the list [l] by [n], e.g., [rotate 1
+[x0; x1; ...; xm]] becomes [x1; ...; xm; x0]. Rotating by a multiple of
+[length l] is the identity function. **)
+Definition rotate {A} (n : nat) (l : list A) : list A :=
+  drop (Z.to_nat (n `mod` length l)%Z) l ++ take (Z.to_nat (n `mod` length l)%Z) l.
+Instance: Params (@rotate) 2 := {}.
+
+(** The function [rotate_take s e l] returns the range between the
+indices [s] (inclusive) and [e] (exclusive) of [l]. If [e ≤ s], all
+elements after [s] and before [e] are returned. *)
+Definition rotate_take {A} (s e : nat) (l : list A) : list A :=
+  take (rotate_nat_sub s e (length l)) (rotate s l).
+Instance: Params (@rotate_take) 3 := {}.
+
 (** The function [reverse l] returns the elements of [l] in reverse order. *)
 Definition reverse {A} (l : list A) : list A := rev_append l [].
 Instance: Params (@reverse) 1 := {}.
@@ -1354,6 +1368,97 @@ Lemma lookup_total_resize_old `{!Inhabited A} l n x i :
   n ≤ i → resize n x l !!! i = inhabitant.
 Proof. intros. by rewrite !list_lookup_total_alt, lookup_resize_old. Qed.
 
+(** ** Properties of the [rotate] function *)
+Lemma rotate_replicate n1 n2 x:
+  rotate n1 (replicate n2 x) = replicate n2 x.
+Proof.
+  unfold rotate. rewrite drop_replicate, take_replicate, <-replicate_plus.
+  f_equal. lia.
+Qed.
+
+Lemma rotate_length l n:
+  length (rotate n l) = length l.
+Proof. unfold rotate. rewrite app_length, drop_length, take_length. lia. Qed.
+
+Lemma lookup_rotate_r l n i:
+  i < length l →
+  rotate n l !! i = l !! rotate_nat_add n i (length l).
+Proof.
+  intros Hlen. destruct (Z_mod_lt n (length l)) as [??];[lia|].
+  assert (Z.to_nat (n `mod` length l) < length l) as Hr.
+  { apply Nat2Z.inj_lt. rewrite Z2Nat.id; lia. }
+  unfold rotate. rewrite rotate_nat_add_add_mod, rotate_nat_add_alt by done.
+  remember (Z.to_nat (n `mod` length l)) as n'.
+  case_decide.
+  - by rewrite lookup_app_l, lookup_drop by (rewrite drop_length; lia).
+  - rewrite lookup_app_r, lookup_take, drop_length by (rewrite drop_length; lia).
+    f_equal. lia.
+Qed.
+
+Lemma lookup_rotate_r_Some l n i x:
+  rotate n l !! i = Some x ↔
+  l !! rotate_nat_add n i (length l) = Some x ∧ i < length l.
+Proof.
+  split.
+  - intros Hl. pose proof (lookup_lt_Some _ _ _ Hl) as Hlen.
+    rewrite rotate_length in Hlen. by rewrite <-lookup_rotate_r.
+  - intros [??]. by rewrite lookup_rotate_r.
+Qed.
+
+Lemma lookup_rotate_l l n i:
+  i < length l → rotate n l !! rotate_nat_sub n i (length l) = l !! i.
+Proof.
+  intros ?. rewrite lookup_rotate_r, rotate_nat_add_sub;[done..|].
+  apply rotate_nat_sub_lt. lia.
+Qed.
+
+Lemma elem_of_rotate l n x:
+  x ∈ rotate n l ↔ x ∈ l.
+Proof.
+  unfold rotate. rewrite <-(take_drop (Z.to_nat (n `mod` length l)) l) at 5.
+  rewrite !elem_of_app. naive_solver.
+Qed.
+
+Lemma rotate_insert_l l n i x:
+  i < length l →
+  rotate n (<[rotate_nat_add n i (length l):=x]> l) = <[i:=x]> (rotate n l).
+Proof.
+  intros Hlen. destruct (Z_mod_lt n (length l)) as [Hn1 Hn2];[lia|].
+  assert (Z.to_nat (n `mod` length l) < length l) as Hr.
+  { apply Nat2Z.inj_lt. rewrite Z2Nat.id; lia. } unfold rotate.
+  rewrite insert_length, rotate_nat_add_add_mod, rotate_nat_add_alt by done.
+  remember (Z.to_nat (n `mod` length l)) as n'.
+  case_decide.
+  - rewrite take_insert, drop_insert_le, insert_app_l
+      by (rewrite ?drop_length; lia). do 2 f_equal. lia.
+  - rewrite take_insert_lt, drop_insert_gt, insert_app_r_alt, drop_length
+      by (rewrite ?drop_length; lia). do 2 f_equal. lia.
+Qed.
+
+Lemma rotate_insert_r l n i x:
+  i < length l →
+  rotate n (<[i:=x]> l) = <[rotate_nat_sub n i (length l):=x]> (rotate n l).
+Proof.
+  intros ?. rewrite <-rotate_insert_l, rotate_nat_add_sub;[done..|].
+  apply rotate_nat_sub_lt. lia.
+Qed.
+
+(** ** Properties of the [rotate_take] function *)
+Lemma rotate_take_insert l s e i x:
+  i < length l →
+  rotate_take s e (<[i:=x]>l) =
+  if decide (rotate_nat_sub s i (length l) < rotate_nat_sub s e (length l)) then
+    <[rotate_nat_sub s i (length l):=x]> (rotate_take s e l) else rotate_take s e l.
+Proof.
+  intros ?. unfold rotate_take. rewrite rotate_insert_r, insert_length by done.
+  case_decide; [rewrite take_insert_lt | rewrite take_insert]; naive_solver lia.
+Qed.
+
+Lemma rotate_take_add l b i :
+  i < length l →
+  rotate_take b (rotate_nat_add b i (length l)) l = take i (rotate b l).
+Proof. intros ?. unfold rotate_take. by rewrite rotate_nat_sub_add. Qed.
+
 (** ** Properties of the [reshape] function *)
 Lemma reshape_length szs l : length (reshape szs l) = length szs.
 Proof. revert l. by induction szs; intros; f_equal/=. Qed.
@@ -2793,6 +2898,19 @@ Section Forall2.
     P x y → Forall2 P (replicate n x) (replicate n y).
   Proof. induction n; simpl; constructor; auto. Qed.
 
+  Lemma Forall2_rotate n l k :
+    Forall2 P l k → Forall2 P (rotate n l) (rotate n k).
+  Proof.
+    intros HAll. unfold rotate. rewrite (Forall2_length _ _ HAll).
+    eauto using Forall2_app, Forall2_take, Forall2_drop.
+  Qed.
+  Lemma Forall2_rotate_take s e l k :
+    Forall2 P l k → Forall2 P (rotate_take s e l) (rotate_take s e k).
+  Proof.
+    intros HAll. unfold rotate_take. rewrite (Forall2_length _ _ HAll).
+    eauto using Forall2_take, Forall2_rotate.
+  Qed.
+
   Lemma Forall2_reverse l k : Forall2 P l k → Forall2 P (reverse l) (reverse k).
   Proof.
     induction 1; rewrite ?reverse_nil, ?reverse_cons; eauto using Forall2_app.
@@ -2943,6 +3061,10 @@ Section Forall2_proper.
 
   Global Instance: Proper (R ==> Forall2 R) (replicate n).
   Proof. repeat intro. eauto using Forall2_replicate. Qed.
+  Global Instance: Proper (Forall2 R ==> Forall2 R) (rotate n).
+  Proof. repeat intro. eauto using Forall2_rotate. Qed.
+  Global Instance: Proper (Forall2 R ==> Forall2 R) (rotate_take s e).
+  Proof. repeat intro. eauto using Forall2_rotate_take. Qed.
   Global Instance: Proper (Forall2 R ==> Forall2 R) reverse.
   Proof. repeat intro. eauto using Forall2_reverse. Qed.
   Global Instance: Proper (Forall2 R ==> option_Forall2 R) last.
@@ -3101,6 +3223,10 @@ Section setoid.
   Proof. intros ???. rewrite !equiv_Forall2. by apply Forall2_filter. Qed.
   Global Instance replicate_proper n : Proper ((≡@{A}) ==> (≡)) (replicate n).
   Proof. induction n; constructor; auto. Qed.
+  Global Instance rotate_proper n : Proper ((≡@{list A}) ==> (≡)) (rotate n).
+  Proof. intros ??. rewrite !equiv_Forall2. by apply Forall2_rotate. Qed.
+  Global Instance rotate_take_proper s e : Proper ((≡@{list A}) ==> (≡)) (rotate_take s e).
+  Proof. intros ??. rewrite !equiv_Forall2. by apply Forall2_rotate_take. Qed.
   Global Instance reverse_proper : Proper ((≡) ==> (≡@{list A})) reverse.
   Proof.
     induction 1; rewrite ?reverse_cons; simpl; [constructor|].
diff --git a/theories/numbers.v b/theories/numbers.v
index d0b9d29d04566e1fda5cbc394844599dd5177f93..46805624ae3951bc8f181c5d83dcbea62c3c8af3 100644
--- a/theories/numbers.v
+++ b/theories/numbers.v
@@ -452,6 +452,22 @@ Proof.
     apply Nat.mod_bound_pos; lia. }
   by rewrite <-Nat2Z.inj_mul, <-Nat2Z.inj_add, <-Nat.div_mod.
 Qed.
+Lemma Nat2Z_inj_div x y :
+  0 ≤ x → 0 ≤ y →
+  Z.to_nat (x `div` y) = (Z.to_nat x `div` Z.to_nat y)%nat.
+Proof.
+  intros. destruct (decide (y = 0%nat)); [by subst; destruct x|].
+  pose proof (Z.div_pos x y).
+  apply (inj Z.of_nat). by rewrite Z2Nat_inj_div, !Z2Nat.id by lia.
+Qed.
+Lemma Nat2Z_inj_mod x y :
+  0 ≤ x → 0 ≤ y →
+  Z.to_nat (x `mod` y) = (Z.to_nat x `mod` Z.to_nat y)%nat.
+Proof.
+  intros. destruct (decide (y = 0%nat)); [by subst; destruct x|].
+  pose proof (Z_mod_pos x y).
+  apply (inj Z.of_nat). by rewrite Z2Nat_inj_mod, !Z2Nat.id by lia.
+Qed.
 Lemma Z_succ_pred_induction y (P : Z → Prop) :
   P y →
   (∀ x, y ≤ x → P x → P (Z.succ x)) →
@@ -808,3 +824,122 @@ Qed.
 
 Lemma Qp_ge_0 (q: Qp): (0 ≤ q)%Qc.
 Proof. apply Qclt_le_weak, Qp_prf. Qed.
+
+(** * Helper for working with accessing lists with wrap-around
+    See also [rotate] and [rotate_take] in [list.v] *)
+(** [rotate_nat_add base offset len] computes [(base + offset) `mod`
+len]. This is useful in combination with the [rotate] function on
+lists, since the index [i] of [rotate n l] corresponds to the index
+[rotate_nat_add n i (length i)] of the original list. The definition
+uses [Z] for consistency with [rotate_nat_sub]. **)
+Definition rotate_nat_add (base offset len : nat) : nat :=
+  Z.to_nat ((base + offset) `mod` len)%Z.
+(** [rotate_nat_sub base offset len] is the inverse of [rotate_nat_add
+base offset len]. The definition needs to use modulo on [Z] instead of
+on nat since otherwise we need the sidecondition [base < len] on
+[rotate_nat_sub_add]. **)
+Definition rotate_nat_sub (base offset len : nat) : nat :=
+  Z.to_nat ((len + offset - base) `mod` len)%Z.
+
+Lemma rotate_nat_add_len_0 base offset:
+  rotate_nat_add base offset 0 = 0.
+Proof. unfold rotate_nat_add. by rewrite Zmod_0_r. Qed.
+Lemma rotate_nat_sub_len_0 base offset:
+  rotate_nat_sub base offset 0 = 0.
+Proof. unfold rotate_nat_sub. by rewrite Zmod_0_r. Qed.
+
+Lemma rotate_nat_add_add_mod base offset len:
+  rotate_nat_add base offset len =
+  rotate_nat_add (Z.to_nat (base `mod` len)%Z) offset len.
+Proof.
+  destruct len as [|i];[ by rewrite !rotate_nat_add_len_0|].
+  pose proof (Z_mod_lt base (S i)) as Hlt. unfold rotate_nat_add.
+  rewrite !Z2Nat.id by lia. by rewrite Zplus_mod_idemp_l.
+Qed.
+
+Lemma rotate_nat_add_alt base offset len:
+  base < len → offset < len →
+  rotate_nat_add base offset len =
+  if decide (base + offset < len) then base + offset else base + offset - len.
+Proof.
+  unfold rotate_nat_add. intros ??. case_decide.
+  - rewrite Z.mod_small by lia. by rewrite <-Nat2Z.inj_add, Nat2Z.id.
+  - rewrite (Zmod_in_range 1) by lia.
+    by rewrite Z.mul_1_l, <-Nat2Z.inj_add, <-Nat2Z.inj_sub,Nat2Z.id by lia.
+Qed.
+Lemma rotate_nat_sub_alt base offset len:
+  base < len → offset < len →
+  rotate_nat_sub base offset len =
+  if decide (offset < base) then len + offset - base else offset - base.
+Proof.
+  unfold rotate_nat_sub. intros ??. case_decide.
+  - rewrite Z.mod_small by lia.
+    by rewrite <-Nat2Z.inj_add, <-Nat2Z.inj_sub, Nat2Z.id by lia.
+  - rewrite (Zmod_in_range 1) by lia.
+    rewrite Z.mul_1_l, <-Nat2Z.inj_add, <-!Nat2Z.inj_sub,Nat2Z.id; lia.
+Qed.
+
+Lemma rotate_nat_add_0 base len :
+  base < len → rotate_nat_add base 0 len = base.
+Proof.
+  intros ?. unfold rotate_nat_add.
+  rewrite Z.mod_small by lia. by rewrite Z.add_0_r, Nat2Z.id.
+Qed.
+Lemma rotate_nat_sub_0 base len :
+  base < len → rotate_nat_sub base base len = 0.
+Proof. intros ?. rewrite rotate_nat_sub_alt by done. case_decide; lia. Qed.
+
+Lemma rotate_nat_add_lt base offset len :
+  0 < len → rotate_nat_add base offset len < len.
+Proof.
+  unfold rotate_nat_add. intros ?.
+  pose proof (Nat.mod_upper_bound (base + offset) len).
+  rewrite Nat2Z_inj_mod, Z2Nat.inj_add, !Nat2Z.id; lia.
+Qed.
+Lemma rotate_nat_sub_lt base offset len :
+  0 < len → rotate_nat_sub base offset len < len.
+Proof.
+  unfold rotate_nat_sub. intros ?.
+  pose proof (Z_mod_lt (len + offset - base) len).
+  apply Nat2Z.inj_lt. rewrite Z2Nat.id; lia.
+Qed.
+
+Lemma rotate_nat_add_sub base len offset:
+  offset < len →
+  rotate_nat_add base (rotate_nat_sub base offset len) len = offset.
+Proof.
+  intros ?. unfold rotate_nat_add, rotate_nat_sub.
+  rewrite Z2Nat.id by (apply Z_mod_pos; lia). rewrite Zplus_mod_idemp_r.
+  replace (base + (len + offset - base))%Z with (len + offset)%Z by lia.
+  rewrite (Zmod_in_range 1) by lia.
+  rewrite Z.mul_1_l, <-Nat2Z.inj_add, <-!Nat2Z.inj_sub,Nat2Z.id; lia.
+Qed.
+
+Lemma rotate_nat_sub_add base len offset:
+  offset < len →
+  rotate_nat_sub base (rotate_nat_add base offset len) len = offset.
+Proof.
+  intros ?. unfold rotate_nat_add, rotate_nat_sub.
+  rewrite Z2Nat.id by (apply Z_mod_pos; lia).
+  assert (∀ n, (len + n - base) = ((len - base) + n))%Z as -> by naive_solver lia.
+  rewrite Zplus_mod_idemp_r.
+  replace (len - base + (base + offset))%Z with (len + offset)%Z by lia.
+  rewrite (Zmod_in_range 1) by lia.
+  rewrite Z.mul_1_l, <-Nat2Z.inj_add, <-!Nat2Z.inj_sub,Nat2Z.id; lia.
+Qed.
+
+Lemma rotate_nat_add_add base offset len n:
+  0 < len →
+  rotate_nat_add base (offset + n) len =
+  (rotate_nat_add base offset len + n) `mod` len.
+Proof.
+  intros ?. unfold rotate_nat_add.
+  rewrite !Nat2Z_inj_mod, !Z2Nat.inj_add, !Nat2Z.id by lia.
+  by rewrite plus_assoc, Nat.add_mod_idemp_l by lia.
+Qed.
+
+Lemma rotate_nat_add_S base offset len:
+  0 < len →
+  rotate_nat_add base (S offset) len =
+  S (rotate_nat_add base offset len) `mod` len.
+Proof. intros ?. by rewrite <-Nat.add_1_r, rotate_nat_add_add, Nat.add_1_r. Qed.
diff --git a/theories/sets.v b/theories/sets.v
index 3462f1baafa10c7e5d4d114d9fa3bb10a0563ca4..2a97d74215afbd7b65b4e6c0e489d10f9d60d489 100644
--- a/theories/sets.v
+++ b/theories/sets.v
@@ -300,6 +300,10 @@ Section set_unfold_list.
     constructor. rewrite elem_of_list_fmap. f_equiv; intros y.
     by rewrite (set_unfold_elem_of y l (P y)).
   Qed.
+  Global Instance set_unfold_rotate x l P n:
+    SetUnfoldElemOf x l P → SetUnfoldElemOf x (rotate n l) P.
+  Proof. constructor. by rewrite elem_of_rotate, (set_unfold_elem_of x l P). Qed.
+
 End set_unfold_list.
 
 Tactic Notation "set_unfold" :=