From 4783c09278d11c5bf5f2629010fe488a2724f3e9 Mon Sep 17 00:00:00 2001
From: seebees <ryanemer@amazon.com>
Date: Tue, 22 Oct 2024 12:53:50 -0700
Subject: [PATCH 01/26] feat: Optimize sort by `Below`

The `Below` function is in the hot path.
The slice (x[1..]) operation is not optimized in Dafny.
This optimizes this function by turning the recursive slice
into a loop over an index into the seq.
Further, a bounded integer version is also included.
---
 .../StructuredEncryption/src/SortCanon.dfy    | 80 ++++++++++++++++++-
 1 file changed, 79 insertions(+), 1 deletion(-)

diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/SortCanon.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/SortCanon.dfy
index 1033d899e..f9c977cbf 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/SortCanon.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/SortCanon.dfy
@@ -148,11 +148,89 @@ module SortCanon {
     }
   }
 
-  predicate method Below(x: seq<uint8>, y: seq<uint8>) {
+  predicate Below(x: seq<uint8>, y: seq<uint8>) {
     |x| != 0 ==>
       && |y| != 0
       && x[0] <= y[0]
       && (x[0] == y[0] ==> Below(x[1..], y[1..]))
+  } by method {
+
+    // The slice x[1..], y[1..] are un-optimized operations in Dafny.
+    // This means that their usage will result in a lot of data copying.
+    // Additional, it is very likely that these size of these sequences
+    // will be less than uint64.
+    // So writing an optimized version that only works on bounded types
+    // should further optimized this hot code.
+
+    if HasUint64Len(x) && HasUint64Len(y) {
+      return BoundedBelow(x,y);
+    }
+
+    if |x| == 0 {
+      assert Below(x, y);
+      return true;
+    }
+
+    if |y| == 0 {
+      assert !Below(x, y);
+      return false;
+    }
+
+    for i := 0 to |x|
+      invariant i <= |y|
+      // The function on the initial arguments
+      // is equal to function applied to the intermediate arguments.
+      invariant Below(x, y) == Below(x[i..], y[i..])
+    {
+      if |y| <= i {
+        return false;
+      } else if y[i] < x[i] {
+        return false;
+      } else if x[i] < y[i] {
+        return true;
+      } else {
+        assert x[i] == y[i];
+      }
+    }
+
+    return true;
+  }
+
+  predicate BoundedBelow(x: seq64<uint8>, y: seq64<uint8>)
+  {
+    Below(x,y)
+  } by method {
+    var xLength := |x| as uint64;
+    var yLength := |y| as uint64;
+
+    if xLength == 0 {
+      assert BoundedBelow(x, y);
+      return true;
+    }
+
+    if yLength == 0 {
+      assert !BoundedBelow(x, y);
+      return false;
+    }
+
+    for i := 0 to xLength
+      invariant i <= yLength
+      // The function on the initial arguments
+      // is equal to function applied to the intermediate arguments.
+      invariant BoundedBelow(x, y) == BoundedBelow(x[i..], y[i..])
+    {
+      if yLength <= i {
+        return false;
+      } else if y[i] < x[i] {
+        return false;
+      } else if x[i] < y[i] {
+        return true;
+      } else {
+        assert x[i] == y[i];
+      }
+    }
+
+    return true;
   }
 
   lemma BelowIsTotal()

From b4a071aac043179d4beeef22e57c3096df0452c7 Mon Sep 17 00:00:00 2001
From: seebees <ryanemer@amazon.com>
Date: Sat, 26 Oct 2024 06:43:00 -0700
Subject: [PATCH 02/26] Compatability things

---
 .../src/DynamoDbEncryptionBranchKeyIdSupplier.dfy               | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoDbEncryptionBranchKeyIdSupplier.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoDbEncryptionBranchKeyIdSupplier.dfy
index 4533b8def..c79ca3294 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoDbEncryptionBranchKeyIdSupplier.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoDbEncryptionBranchKeyIdSupplier.dfy
@@ -70,7 +70,7 @@ module DynamoDbEncryptionBranchKeyIdSupplier {
     // We expect this interface to be implemented in the native language,
     // so any errors thrown by the native implementation will appear as Opaque errors
     if err.Opaque? then
-      MPL.Opaque(obj:=err.obj)
+      MPL.Opaque(obj:=err.obj, alt_text:="")
     else
       MPL.AwsCryptographicMaterialProvidersException(message:="Unexpected error while getting Branch Key ID.")
   }

From ea794cc2b84497c3d5781b66ebd988c6f319822f Mon Sep 17 00:00:00 2001
From: seebees <ryanemer@amazon.com>
Date: Thu, 31 Oct 2024 09:32:52 -0700
Subject: [PATCH 03/26] feat: Optimize sort by `MergeSort`

The `MergeSort` function is in the hot path.
The slice (x[1..]) operation is not optimized in Dafny.
This optimizes this function by turning the recursive slice
into a loop over an index into the seq.
Further, a bounded integer version is also included.

It also limits the total amount of data copied.
---
 .../src/OptimizedMergeSort.dfy                | 515 ++++++++++++++++++
 .../StructuredEncryption/src/SortCanon.dfy    |  16 +-
 2 files changed, 529 insertions(+), 2 deletions(-)
 create mode 100644 DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy

diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
new file mode 100644
index 000000000..a9b3d12a5
--- /dev/null
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
@@ -0,0 +1,515 @@
+// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+include "../Model/AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes.dfy"
+
+module {:options "-functionSyntax:4"} OptimizedMergeSort {
+  import Seq.MergeSort
+  import Relations = MergeSort.Relations
+  import opened StandardLibrary.UInt
+
+  // The Seq.MergeSort function implemented as implemented
+  // does not compile to an optimal implementation
+  // in any of the Dafny target languages.
+  // This implementation aims to be significantly more optimal.
+  // First, it minimizes copies.
+  // It does this by making 2 arrays of the original sequence
+  // and then using these 2 as left and right alternatively.
+  // This can be audited by verifying
+  // that the arrays are only sliced into a seq in `FastMergeSort`.
+  // All other slicing is done in ghost state.
+  // Second, is has a bounded number implementation
+  // that avoids using `nat`.
+
+  function {:isolate_assertions} FastMergeSort<T(!new)>(s: seq<T>, lessThanOrEq: (T, T) -> bool): (result :seq<T>)
+    requires Relations.TotalOrdering(lessThanOrEq)
+  {
+    MergeSort.MergeSortBy(s, lessThanOrEq)
+  }
+  by method {
+    if |s| <= 1 {
+      return s;
+    } else {
+
+      // The slice x[1..], y[1..] are un-optimized operations in Dafny.
+      // This means that their usage will result in a lot of data copying.
+      // Additional, it is very likely that these size of these sequences
+      // will be less than uint64.
+      // So writing an optimized version that only works on bounded types
+      // should further optimized this hot code.
+
+      var left := new T[|s|](i requires 0 <= i < |s| => s[i]);
+      var right := new T[|s|](i requires 0 <= i < |s| => s[i]);
+      var lo, hi := 0, right.Length;
+
+      label BEFORE_WORK:
+
+      if HasUint64Len(s) {
+        var boundedLo: uint64, boundedHi: uint64 := 0, right.Length as uint64;
+        ghost var _ := BoundedMergeSortMethod(left, right, lessThanOrEq, boundedLo, boundedHi, Right);
+
+        result :=  right[..];
+      } else {
+        ghost var _ := MergeSortMethod(left, right, lessThanOrEq, lo, hi, Right);
+
+        result :=  right[..];
+      }
+
+      ghost var other := MergeSort.MergeSortBy(s, lessThanOrEq);
+
+      assert Relations.SortedBy(right[..], lessThanOrEq) by {
+        assert right[..] == right[lo..hi];
+      }
+      assert multiset(right[..]) == multiset(other) by {
+        calc {
+          multiset(right[..]);
+        == {assert right[..] == right[lo..hi];}
+          multiset(right[lo..hi]);
+        ==
+          multiset(old@BEFORE_WORK(left[lo..hi]));
+        == {assert old@BEFORE_WORK(left[lo..hi]) == s;}
+          multiset(s);
+        ==
+          multiset(other);
+        }
+      }
+
+      // Implementing a by method can be complicated.
+      // Because methods can have non-determinism,
+      // where functions can not.
+      // This means that Dafny normally wants to know
+      // that the method and function maintain equality at every step.
+      // But this is hard for this kind of optimized sorting.
+      // Because what is the functional state at every step
+      // and how does it correspond the state of 2 optimized arrays?
+      // This lemma works around this
+      // by proving that the outcomes are always deterministic and the same.
+      // It does this by proving that given a total ordering,
+      // there is one and only one way to sort a given sequence. 
+      TotalOrderingImpliesSortingIsUnique(right[..], other, lessThanOrEq);
+    }
+  }
+
+  datatype PlaceResults = Left | Right | Either
+  type ResultPlacement = r: PlaceResults | !r.Either? witness *
+
+  method {:isolate_assertions} MergeSortMethod<T(!new)>(
+    left: array<T>,
+    right: array<T>,
+    lessThanOrEq: (T, T) -> bool,
+    lo: nat,
+    hi: nat,
+    where: PlaceResults
+  )
+    returns (resultPlacement: ResultPlacement)
+    requires Relations.TotalOrdering(lessThanOrEq)
+    requires lo < hi <= left.Length
+    requires hi <= right.Length && left != right
+    // reads left, right
+    modifies left, right
+    ensures !where.Either? ==> where == resultPlacement
+
+    // We do not modify anything before lo
+    ensures left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo])
+    // We do not modify anything above hi
+    ensures left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..])
+
+    ensures multiset(left[lo..hi]) == multiset(old(left[lo..hi]))
+    ensures resultPlacement.Left? ==> Relations.SortedBy(left[lo..hi], lessThanOrEq)
+    ensures resultPlacement.Right? ==> Relations.SortedBy(right[lo..hi], lessThanOrEq)
+    ensures resultPlacement.Right? ==> multiset(right[lo..hi]) == multiset(old(left[lo..hi]))
+
+    decreases hi - lo
+  {
+    if hi - lo == 1 {
+      if where == Right {
+        right[lo] := left[lo];
+        return Right;
+      } else {
+        return Left;
+      }
+    }
+
+    ghost var beforeWork := multiset(left[lo..hi]);
+    var mid := (lo + hi) / 2;
+    var placement? := MergeSortMethod(left, right, lessThanOrEq, lo, mid, Either);
+    assert left[mid..hi] == old(left[mid..hi]);
+    ghost var placement2? := MergeSortMethod(left, right, lessThanOrEq, mid, hi, placement?);
+    assert placement2? == placement?;
+
+    ghost var preMergeResult := if placement?.Left? then left else right;
+    calc {
+      multiset(preMergeResult[lo..hi]);
+    == { assert preMergeResult[lo..hi] == preMergeResult[lo..mid] + preMergeResult[mid..hi]; }
+      multiset(preMergeResult[lo..mid] + preMergeResult[mid..hi]);
+    ==
+      multiset(old(left[lo..mid]) + old(left[mid..hi]));
+    ==  { assert old(left[lo..hi]) == old(left[lo..mid]) + old(left[mid..hi]); }
+      beforeWork;
+    }
+
+    ghost var mergedResult;
+    if placement?.Left? {
+      MergeIntoRight(left := left, right := right, lessThanOrEq := lessThanOrEq, lo := lo, mid := mid, hi := hi);
+      resultPlacement := Right;
+
+      mergedResult := right[lo..hi];
+      assert left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..]);
+    } else {
+      assert placement?.Right?;
+      MergeIntoRight(left := right, right := left, lessThanOrEq := lessThanOrEq, lo := lo, mid := mid, hi := hi);
+      resultPlacement := Left;
+
+      mergedResult := left[lo..hi];
+      assert left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..]);
+    }
+
+    label BEFORE_RETURN:
+    assert left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo]);
+    if resultPlacement.Left? && where == Right {
+      forall i | lo <= i < hi {
+        right[i] := left[i];
+      }
+
+      assert right[lo..hi] == mergedResult;
+      assert left[..] == old@BEFORE_RETURN(left[..]);
+      assert right[..lo] == old(right[..lo]);
+
+      resultPlacement := Right;
+    }
+    if resultPlacement.Right? && where == Left {
+      forall i | lo <= i < hi {
+        left[i] := right[i];
+      }
+
+      assert left[lo..hi] == mergedResult;
+      assert right[..] == old@BEFORE_RETURN(right[..]);
+      assert left[..lo] == old(left[..lo]);
+
+      resultPlacement := Left;
+    }
+  }
+
+  method {:isolate_assertions} MergeIntoRight<T(!new)>(
+    nameonly left: array<T>,
+    nameonly right: array<T>,
+    nameonly lessThanOrEq: (T, T) -> bool,
+    nameonly lo: nat,
+    nameonly mid: nat,
+    nameonly hi: nat
+  )
+    requires Relations.TotalOrdering(lessThanOrEq)
+    requires lo <= mid <= hi <= left.Length
+    requires  hi <= right.Length && left != right
+    // We store "left" in [lo..mid]
+    requires Relations.SortedBy(left[lo..mid], lessThanOrEq)
+    // We store "right" in [mid..hi]
+    requires Relations.SortedBy(left[mid..hi], lessThanOrEq)
+    // reads left, right
+    modifies right
+    // We do not modify anything before lo
+    ensures right[..lo] == old(right[..lo]) && left[..lo] == old(left[..lo])
+    // We do not modify anything above hi
+    ensures right[hi..] == old(right[hi..]) && left[..lo] == old(left[..lo])
+    ensures Relations.SortedBy(right[lo..hi], lessThanOrEq)
+    ensures multiset(right[lo..hi]) == multiset(old(left[lo..hi]))
+    ensures multiset(left[lo..hi]) == multiset(old(left[lo..hi]))
+  {
+    var leftPosition, rightPosition, iter := lo, mid, lo;
+    while iter < hi
+      modifies right
+
+      invariant lo <= leftPosition <= mid <= rightPosition <= hi
+      invariant leftPosition - lo + rightPosition - mid == iter - lo
+      invariant right[..lo] == old(right[..lo])
+      invariant right[hi..] == old(right[hi..])
+
+      invariant Relations.SortedBy(left[leftPosition..mid], lessThanOrEq)
+      invariant Relations.SortedBy(left[rightPosition..hi], lessThanOrEq)
+      invariant Below(right[lo..iter], left[leftPosition..mid], lessThanOrEq)
+      invariant Below(right[lo..iter], left[rightPosition..hi], lessThanOrEq)
+      invariant Relations.SortedBy(right[lo..iter], lessThanOrEq)
+      invariant multiset(right[lo..iter]) == multiset(left[lo..leftPosition]) + multiset(left[mid..rightPosition])
+    {
+      if leftPosition == mid || (rightPosition < hi && lessThanOrEq(left[rightPosition], left[leftPosition])) {
+        right[iter] := left[rightPosition];
+
+        PushStillSortedBy(right, lo, iter, lessThanOrEq);
+        rightPosition, iter := rightPosition + 1, iter + 1;
+
+        BelowIsTransitive(right[lo..iter], left[leftPosition..mid], lessThanOrEq);
+        BelowIsTransitive(right[lo..iter], left[rightPosition..hi], lessThanOrEq);
+      } else {
+        right[iter] := left[leftPosition];
+
+        PushStillSortedBy(right, lo, iter, lessThanOrEq);
+        leftPosition, iter := leftPosition + 1, iter + 1;
+
+        BelowIsTransitive(right[lo..iter], left[leftPosition..mid], lessThanOrEq);
+        BelowIsTransitive(right[lo..iter], left[rightPosition..hi], lessThanOrEq);
+      }
+    }
+    assert multiset(right[lo..hi]) == multiset(old(left[lo..hi])) by {
+      assert leftPosition == mid && rightPosition == hi;
+      assert old(left[lo..hi]) == left[lo..hi] == left[lo..mid] + left[mid..hi];
+    }
+  }
+
+  // Helpers to prove MergeSort
+
+  ghost predicate Below<T(!new)>(a: seq<T>, b: seq<T>, lessThanOrEq: (T, T) -> bool)
+    requires Relations.TotalOrdering(lessThanOrEq)
+  {
+    forall i, j :: 0 <= i < |a| && 0 <= j < |b| ==> lessThanOrEq(a[i], b[j])
+  }
+
+  lemma BelowIsTransitive<T(!new)>(a: seq<T>, b: seq<T>, lessThanOrEq: (T, T) -> bool)
+    requires Relations.TotalOrdering(lessThanOrEq)
+    requires Relations.SortedBy(a, lessThanOrEq)
+    requires Relations.SortedBy(b, lessThanOrEq)
+    requires 0 < |a| && 0 < |b| ==> lessThanOrEq(a[|a| - 1], b[0])
+    ensures Below(a, b, lessThanOrEq)
+  {}
+
+  lemma PushStillSortedBy<T(!new)>(a: array<T>, lo:nat, i: nat, lessThanOrEq: (T, T) -> bool)
+    requires 0 <= lo <= i < a.Length
+    requires Relations.SortedBy(a[lo..i], lessThanOrEq)
+    requires |a[lo..i]| == 0 || lessThanOrEq(a[lo..i][|a[lo..i]| - 1], a[i])
+    requires Relations.TotalOrdering(lessThanOrEq)
+    ensures Relations.SortedBy(a[lo..i + 1], lessThanOrEq)
+    ensures lo < i ==> lessThanOrEq(a[i - 1], a[i])
+  {}
+
+  lemma {:isolate_assertions} TotalOrderingImpliesSortingIsUnique<T(!new)>(s1: seq<T>, s2: seq<T>, lessThanOrEq: (T, T) -> bool)
+    requires Relations.TotalOrdering(lessThanOrEq)
+    requires Relations.SortedBy(s1, lessThanOrEq) && Relations.SortedBy(s2, lessThanOrEq)
+    requires multiset(s1) == multiset(s2)
+    ensures s1 == s2
+  {
+    if |s1| == 0 {
+    } else {
+      assert s1[0] in s2 by {
+        assert s1[0] in multiset(s2);
+      }
+
+      var i :| 0 <= i < |s2| && s2[i] == s1[0];
+      assert multiset{s1[0]} == multiset{s2[i]};
+      assert multiset{s1[0]} + multiset(s1[1..]) == multiset(s1) by {
+        assert s1 == [s1[0]] + s1[1..];
+      }
+      assert multiset{s2[i]} + multiset(s2[0..i] + s2[i+1..]) == multiset(s2) by {
+        assert s2 == s2[0..i] + [s2[i]] + s2[i+1..];
+      }
+
+      assert Relations.SortedBy(s1[1..], lessThanOrEq);
+      assert Relations.SortedBy(s2[0..i] + s2[i+1..], lessThanOrEq) by {
+        if i == 0 || i == |s2| - 1 {
+        } else {
+          assert lessThanOrEq(s2[i-1], s2[i]);
+          assert lessThanOrEq(s2[i], s2[i+1]);
+        }
+      }
+      MultisetProperty(multiset{s1[0]}, multiset(s1[1..]), multiset(s2[0..i] + s2[i+1..]));
+      TotalOrderingImpliesSortingIsUnique(s1[1..], s2[0..i] + s2[i+1..], lessThanOrEq);
+
+      if i == 0 {
+      } else {
+        assert s1 == [s2[i]] + s1[1..];
+        assert lessThanOrEq(s2[0], s2[i]);
+        assert s2[0] in s1;
+      }
+    }
+  }
+
+  lemma MultisetProperty(m: multiset, a: multiset, b: multiset)
+    requires m + a == m + b
+    ensures a == b
+  {
+    var a' := (m + a) - m;
+    var b' := (m + b) - m;
+    assert a == a' == b' == b;
+  }
+
+  // These are bounded implementations of the above.
+  // They do exactly the same thing,
+  // but they use `uint64`.
+  // This further speeds things up
+  // because math with bounded variables
+  // is significantly faster that math with big numbers.
+
+  method {:isolate_assertions} BoundedMergeSortMethod<T(!new)>(
+    left: array<T>,
+    right: array<T>,
+    lessThanOrEq: (T, T) -> bool,
+    lo: uint64,
+    hi: uint64,
+    where: PlaceResults
+  )
+    returns (resultPlacement: ResultPlacement)
+    requires Relations.TotalOrdering(lessThanOrEq)
+    requires
+      && left.Length < UINT64_LIMIT
+      && right.Length < UINT64_LIMIT
+    requires lo < hi <= left.Length as uint64
+    requires hi <= right.Length as uint64 && left != right
+    // reads left, right
+    modifies left, right
+    ensures !where.Either? ==> where == resultPlacement
+
+    // We do not modify anything before lo
+    ensures left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo])
+    // We do not modify anything above hi
+    ensures left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..])
+
+    ensures multiset(left[lo..hi]) == multiset(old(left[lo..hi]))
+    ensures resultPlacement.Left? ==> Relations.SortedBy(left[lo..hi], lessThanOrEq)
+    ensures resultPlacement.Right? ==> Relations.SortedBy(right[lo..hi], lessThanOrEq)
+    ensures resultPlacement.Right? ==> multiset(right[lo..hi]) == multiset(old(left[lo..hi]))
+
+    decreases hi - lo
+  {
+    if hi - lo == 1 {
+      if where == Right {
+        right[lo] := left[lo];
+        return Right;
+      } else {
+        return Left;
+      }
+    }
+
+    ghost var beforeWork := multiset(left[lo..hi]);
+    var mid :=  ((hi - lo)/2) + lo;
+    var placement? := BoundedMergeSortMethod(left, right, lessThanOrEq, lo, mid, Either);
+    assert left[mid..hi] == old(left[mid..hi]);
+    ghost var placement2? := BoundedMergeSortMethod(left, right, lessThanOrEq, mid, hi, placement?);
+    assert placement2? == placement?;
+
+    ghost var preMergeResult := if placement?.Left? then left else right;
+    calc {
+      multiset(preMergeResult[lo..hi]);
+    == { assert preMergeResult[lo..hi] == preMergeResult[lo..mid] + preMergeResult[mid..hi]; }
+      multiset(preMergeResult[lo..mid] + preMergeResult[mid..hi]);
+    ==
+      multiset(old(left[lo..mid]) + old(left[mid..hi]));
+    ==  { assert old(left[lo..hi]) == old(left[lo..mid]) + old(left[mid..hi]); }
+      beforeWork;
+    }
+
+    ghost var mergedResult;
+    if placement?.Left? {
+      BoundedMergeIntoRight(left := left, right := right, lessThanOrEq := lessThanOrEq, lo := lo, mid := mid, hi := hi);
+      resultPlacement, mergedResult := Right, right[lo..hi];
+
+      assert left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..]);
+    } else {
+      assert placement?.Right?;
+      BoundedMergeIntoRight(left := right, right := left, lessThanOrEq := lessThanOrEq, lo := lo, mid := mid, hi := hi);
+      resultPlacement, mergedResult := Left, left[lo..hi];
+
+      assert left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..]);
+    }
+
+    label BEFORE_RETURN:
+    assert left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo]);
+    if resultPlacement.Left? && where == Right {
+      forall i | lo <= i < hi {
+        right[i] := left[i];
+      }
+
+      assert right[lo..hi] == mergedResult;
+      assert left[..] == old@BEFORE_RETURN(left[..]);
+      assert right[..lo] == old(right[..lo]);
+
+      resultPlacement := Right;
+    }
+    if resultPlacement.Right? && where == Left {
+      forall i | lo <= i < hi {
+        left[i] := right[i];
+      }
+      assert left[lo..hi] == mergedResult;
+      assert right[..] == old@BEFORE_RETURN(right[..]);
+      assert left[..lo] == old(left[..lo]);
+
+      resultPlacement := Left;
+    }
+  }
+
+  method {:isolate_assertions} BoundedMergeIntoRight<T(!new)>(
+    nameonly left: array<T>,
+    nameonly right: array<T>,
+    nameonly lessThanOrEq: (T, T) -> bool,
+    nameonly lo: uint64,
+    nameonly mid: uint64,
+    nameonly hi: uint64
+  )
+    requires Relations.TotalOrdering(lessThanOrEq)
+    requires
+      && left.Length < UINT64_LIMIT
+      && right.Length < UINT64_LIMIT
+    requires lo <= mid <= hi <= left.Length as uint64
+    requires  hi <= right.Length as uint64 && left != right
+    // We store "left" in [lo..mid]
+    requires Relations.SortedBy(left[lo..mid], lessThanOrEq)
+    // We store "right" in [mid..hi]
+    requires Relations.SortedBy(left[mid..hi], lessThanOrEq)
+    // reads left, right
+    modifies right
+    // We do not modify anything before lo
+    ensures right[..lo] == old(right[..lo]) && left[..lo] == old(left[..lo])
+    // We do not modify anything above hi
+    ensures right[hi..] == old(right[hi..]) && left[..lo] == old(left[..lo])
+    ensures Relations.SortedBy(right[lo..hi], lessThanOrEq)
+    ensures multiset(right[lo..hi]) == multiset(old(left[lo..hi]))
+    ensures multiset(left[lo..hi]) == multiset(old(left[lo..hi]))
+  {
+    var leftPosition, rightPosition, iter := lo, mid, lo;
+    while iter < hi
+      modifies right
+
+      invariant lo <= leftPosition <= mid <= rightPosition <= hi
+      invariant leftPosition as nat - lo as nat + rightPosition as nat - mid as nat == iter as nat - lo as nat
+      invariant right[..lo] == old(right[..lo])
+      invariant right[hi..] == old(right[hi..])
+
+      invariant Relations.SortedBy(left[leftPosition..mid], lessThanOrEq)
+      invariant Relations.SortedBy(left[rightPosition..hi], lessThanOrEq)
+      invariant Below(right[lo..iter], left[leftPosition..mid], lessThanOrEq)
+      invariant Below(right[lo..iter], left[rightPosition..hi], lessThanOrEq)
+      invariant Relations.SortedBy(right[lo..iter], lessThanOrEq)
+      invariant multiset(right[lo..iter]) == multiset(left[lo..leftPosition]) + multiset(left[mid..rightPosition])
+    {
+      label BEFORE_WORK:
+      if leftPosition == mid || (rightPosition < hi && lessThanOrEq(left[rightPosition], left[leftPosition])) {
+        right[iter] := left[rightPosition];
+
+        PushStillSortedBy(right, lo as nat, iter as nat, lessThanOrEq);
+        rightPosition, iter := rightPosition + 1, iter + 1;
+
+        BelowIsTransitive(right[lo..iter], left[leftPosition..mid], lessThanOrEq);
+
+        assert 0 < |right[lo..iter]| && 0 < |left[rightPosition..hi]| ==> lessThanOrEq(right[lo..iter][|right[lo..iter]| - 1], left[rightPosition..hi][0]);
+        BelowIsTransitive(right[lo..iter], left[rightPosition..hi], lessThanOrEq);
+      } else {
+        right[iter] := left[leftPosition];
+
+        PushStillSortedBy(right, lo as nat, iter as nat, lessThanOrEq);
+        leftPosition, iter := leftPosition + 1, iter + 1;
+
+        assert 0 < |right[lo..iter]| && 0 < |left[leftPosition..mid]| ==> lessThanOrEq(right[lo..iter][|right[lo..iter]| - 1], left[leftPosition..mid][0]) by {
+          if 0 < |right[lo..iter]| && 0 < |left[leftPosition..mid]| {
+            assert lessThanOrEq(left[leftPosition-1], left[leftPosition]) by {
+              assert lo <= leftPosition-1 < leftPosition < mid;
+              assert Relations.SortedBy(left[lo..mid], lessThanOrEq);
+            }
+          }
+        }
+        BelowIsTransitive(right[lo..iter], left[leftPosition..mid], lessThanOrEq);
+        BelowIsTransitive(right[lo..iter], left[rightPosition..hi], lessThanOrEq);
+      }
+    }
+    assert multiset(right[lo..hi]) == multiset(old(left[lo..hi])) by {
+      assert leftPosition == mid && rightPosition == hi;
+      assert old(left[lo..hi]) == left[lo..hi] == left[lo..mid] + left[mid..hi];
+    }
+  }
+}
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/SortCanon.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/SortCanon.dfy
index f9c977cbf..ae86e268b 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/SortCanon.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/SortCanon.dfy
@@ -3,6 +3,7 @@
 
 include "../Model/AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes.dfy"
 include "Util.dfy"
+include "OptimizedMergeSort.dfy"
 
 module SortCanon {
   export
@@ -22,6 +23,7 @@ module SortCanon {
   import opened Relations
   import opened Seq.MergeSort
   import opened StructuredEncryptionUtil
+  import OptimizedMergeSort
 
   predicate method AuthBelow(x: CanonAuthItem, y: CanonAuthItem) {
     Below(x.key, y.key)
@@ -295,7 +297,7 @@ module SortCanon {
   {}
 
 
-  function method AuthSort(x : CanonAuthList) : (result : CanonAuthList)
+  function AuthSort(x : CanonAuthList) : (result : CanonAuthList)
     requires CanonAuthListHasNoDuplicates(x)
     ensures multiset(x) == multiset(result)
     ensures SortedBy(result, AuthBelow)
@@ -307,9 +309,14 @@ module SortCanon {
     CanonAuthListMultiNoDup(x, ret);
     assert CanonAuthListHasNoDuplicates(ret);
     ret
+  } by method {
+    AuthBelowIsTotal();
+    result := OptimizedMergeSort.FastMergeSort(x, AuthBelow);
+    CanonAuthListMultiNoDup(x, result);
+    assert CanonAuthListHasNoDuplicates(result);
   }
 
-  function method CryptoSort(x : CanonCryptoList) : (result : CanonCryptoList)
+  function CryptoSort(x : CanonCryptoList) : (result : CanonCryptoList)
     requires CanonCryptoListHasNoDuplicates(x)
     ensures multiset(x) == multiset(result)
     ensures multiset(result) == multiset(x)
@@ -322,6 +329,11 @@ module SortCanon {
     CanonCryptoListMultiNoDup(x, ret);
     assert CanonCryptoListHasNoDuplicates(ret);
     ret
+  } by method {
+    CryptoBelowIsTotal();
+    result := OptimizedMergeSort.FastMergeSort(x, CryptoBelow);
+    CanonCryptoListMultiNoDup(x, result);
+    assert CanonCryptoListHasNoDuplicates(result);
   }
 
   lemma MultisetHasNoDuplicates(xs: CanonCryptoList)

From a41a673416a91cf5c8b47395e24ca9e99f96d088 Mon Sep 17 00:00:00 2001
From: seebees <ryanemer@amazon.com>
Date: Fri, 1 Nov 2024 13:08:12 -0700
Subject: [PATCH 04/26] roll back

---
 .../src/DynamoDbEncryptionBranchKeyIdSupplier.dfy               | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoDbEncryptionBranchKeyIdSupplier.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoDbEncryptionBranchKeyIdSupplier.dfy
index c79ca3294..4533b8def 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoDbEncryptionBranchKeyIdSupplier.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoDbEncryptionBranchKeyIdSupplier.dfy
@@ -70,7 +70,7 @@ module DynamoDbEncryptionBranchKeyIdSupplier {
     // We expect this interface to be implemented in the native language,
     // so any errors thrown by the native implementation will appear as Opaque errors
     if err.Opaque? then
-      MPL.Opaque(obj:=err.obj, alt_text:="")
+      MPL.Opaque(obj:=err.obj)
     else
       MPL.AwsCryptographicMaterialProvidersException(message:="Unexpected error while getting Branch Key ID.")
   }

From 3aa38da1bd6683c86d4fbf54af31cb313fbdc912 Mon Sep 17 00:00:00 2001
From: seebees <ryanemer@amazon.com>
Date: Fri, 1 Nov 2024 20:53:15 -0700
Subject: [PATCH 05/26] do not use for comprehension

---
 .../src/OptimizedMergeSort.dfy                | 25 +++++++++++++++----
 1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
index a9b3d12a5..1bde8c058 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
@@ -85,7 +85,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
       // This lemma works around this
       // by proving that the outcomes are always deterministic and the same.
       // It does this by proving that given a total ordering,
-      // there is one and only one way to sort a given sequence. 
+      // there is one and only one way to sort a given sequence.
       TotalOrderingImpliesSortingIsUnique(right[..], other, lessThanOrEq);
     }
   }
@@ -412,21 +412,36 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     label BEFORE_RETURN:
     assert left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo]);
     if resultPlacement.Left? && where == Right {
-      forall i | lo <= i < hi {
+      for i := lo to hi
+        modifies right
+        invariant left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo])
+        invariant left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..])
+        invariant right[lo..i] == left[lo..i]
+      {
         right[i] := left[i];
       }
 
-      assert right[lo..hi] == mergedResult;
+      assert right[lo..hi] == mergedResult by {
+        assert mergedResult == left[lo..hi];
+      }
       assert left[..] == old@BEFORE_RETURN(left[..]);
       assert right[..lo] == old(right[..lo]);
 
       resultPlacement := Right;
     }
     if resultPlacement.Right? && where == Left {
-      forall i | lo <= i < hi {
+      for i := lo to hi
+        modifies left
+        invariant left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo])
+        invariant left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..])
+        invariant left[lo..i] == right[lo..i]
+      {
         left[i] := right[i];
       }
-      assert left[lo..hi] == mergedResult;
+
+      assert left[lo..hi] == mergedResult by {
+        assert mergedResult == right[lo..hi];
+      }
       assert right[..] == old@BEFORE_RETURN(right[..]);
       assert left[..lo] == old(left[..lo]);
 

From 63789b68bbaea65b1aa437741585b2dfbe6295fb Mon Sep 17 00:00:00 2001
From: seebees <ryanemer@amazon.com>
Date: Mon, 4 Nov 2024 09:59:05 -0800
Subject: [PATCH 06/26] update optimize

---
 .../src/OptimizedMergeSort.dfy                | 22 +++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
index 1bde8c058..5c5152a79 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
@@ -167,7 +167,12 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     label BEFORE_RETURN:
     assert left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo]);
     if resultPlacement.Left? && where == Right {
-      forall i | lo <= i < hi {
+      for i := lo to hi
+        modifies right
+        invariant left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo])
+        invariant left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..])
+        invariant right[lo..i] == left[lo..i]
+      {
         right[i] := left[i];
       }
 
@@ -178,7 +183,12 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
       resultPlacement := Right;
     }
     if resultPlacement.Right? && where == Left {
-      forall i | lo <= i < hi {
+      for i := lo to hi
+        modifies left
+        invariant left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo])
+        invariant left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..])
+        invariant left[lo..i] == right[lo..i]
+      {
         left[i] := right[i];
       }
 
@@ -412,6 +422,14 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     label BEFORE_RETURN:
     assert left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo]);
     if resultPlacement.Left? && where == Right {
+      // A forall comprehension might seem like a nice fit here,
+      // however this does not good for two reasons.
+      // First, Dafny currently creates a range fur the full bounds of the bounded number
+      // see: https://github.com/dafny-lang/dafny/issues/5897
+      // Second this would create two loops.
+      // First loop would create the `lo to hi` range of numbers.
+      // The second loop would then loop over these elements.
+      // A single loop with 
       for i := lo to hi
         modifies right
         invariant left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo])

From d83759abe9a5fdfac7bf9388a406d29c5d648f48 Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Tue, 4 Feb 2025 16:07:23 -0800
Subject: [PATCH 07/26] chore: make EncodeAscii ghost

---
 .../dafny/DynamoDbEncryption/test/Beacon.dfy  |  7 ++-
 ...amoDbEncryptionBranchKeyIdSupplierTest.dfy | 26 ++++++++--
 ...moDbGetEncryptedDataKeyDescriptionTest.dfy | 51 ++++++++++++++++---
 .../src/DynamoDbMiddlewareSupport.dfy         |  5 +-
 .../dafny/DynamoDbItemEncryptor/src/Util.dfy  | 17 +++++--
 .../test/DynamoDBItemEncryptorTest.dfy        |  5 +-
 .../dafny/StructuredEncryption/src/Crypt.dfy  | 20 ++++++--
 .../dafny/StructuredEncryption/src/Footer.dfy | 24 ++++++---
 .../dafny/StructuredEncryption/src/Util.dfy   | 42 ++++++++++++---
 .../test/HappyCaseTests.dfy                   | 12 ++++-
 .../StructuredEncryption/test/Header.dfy      | 36 +++++++++++--
 .../dafny/StructuredEncryption/test/Paths.dfy | 30 +++++------
 .../dafny/DDBEncryption/src/JsonConfig.dfy    |  9 +++-
 submodules/MaterialProviders                  |  2 +-
 14 files changed, 223 insertions(+), 63 deletions(-)

diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/Beacon.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/Beacon.dfy
index a812cdc0e..e0579c635 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/Beacon.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/Beacon.dfy
@@ -18,6 +18,11 @@ module TestBaseBeacon {
   import DDB = ComAmazonawsDynamodbTypes
   import SE = AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes
 
+  const x123 : UTF8.ValidUTF8Bytes :=
+    var s := [0x31, 0x32, 0x33];
+    assert s == UTF8.EncodeAscii("123");
+    s
+
   method {:test} TestBeacon() {
     var primitives :- expect Primitives.AtomicPrimitives();
 
@@ -31,7 +36,7 @@ module TestBaseBeacon {
     expect bytes[7] == 0x80;
     str :- expect b.hash([], key := [1,2]);
     expect str == "80";
-    bytes :- expect bb.getHmac(UTF8.EncodeAscii("123"), key := [1,2]);
+    bytes :- expect bb.getHmac(x123, key := [1,2]);
     expect bytes[7] == 0x61;
   }
 
diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbEncryptionBranchKeyIdSupplierTest.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbEncryptionBranchKeyIdSupplierTest.dfy
index 74e12264f..63cb0b9c3 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbEncryptionBranchKeyIdSupplierTest.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbEncryptionBranchKeyIdSupplierTest.dfy
@@ -27,7 +27,12 @@ module DynamoDbEncryptionBranchKeyIdSupplierTest {
 
   // These tests require a keystore populated with a key with this Id
   const BRANCH_KEY_ID := "75789115-1deb-4fe3-a2ec-be9e885d1945"
-  const BRANCH_KEY_ID_UTF8 := UTF8.EncodeAscii(BRANCH_KEY_ID)
+
+  const BRANCH_KEY_ID_UTF8 : UTF8.ValidUTF8Bytes :=
+    var s := [0x37, 0x35, 0x37, 0x38, 0x39, 0x31, 0x31, 0x35, 0x2d, 0x31, 0x64, 0x65, 0x62, 0x2d, 0x34, 0x66, 0x65, 0x33, 0x2d, 0x61, 0x32, 0x65, 0x63, 0x2d, 0x62, 0x65, 0x39, 0x65, 0x38, 0x38, 0x35, 0x64, 0x31, 0x39, 0x34, 0x35];
+    assert s == UTF8.EncodeAscii(BRANCH_KEY_ID);
+    s
+
   const ALTERNATE_BRANCH_KEY_ID := "4bb57643-07c1-419e-92ad-0df0df149d7c"
 
   // Constants for TestBranchKeySupplier
@@ -41,10 +46,23 @@ module DynamoDbEncryptionBranchKeyIdSupplierTest {
   const CASE_B_BYTES: seq<uint8> := STRING_TYPE_ID + [0x43, 0x41, 0x53, 0x45, 0x5f, 0x42]
   const BRANCH_KEY_ID_A := BRANCH_KEY_ID
   const BRANCH_KEY_ID_B := ALTERNATE_BRANCH_KEY_ID
-  const EC_PARTITION_NAME := UTF8.EncodeAscii("aws-crypto-partition-name")
+
+  const EC_PARTITION_NAME : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2d, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6e, 0x61, 0x6d, 0x65];
+    assert s == UTF8.EncodeAscii("aws-crypto-partition-name");
+    s
+
   const RESERVED_PREFIX := "aws-crypto-attr."
-  const KEY_ATTR_NAME := UTF8.EncodeAscii(RESERVED_PREFIX + BRANCH_KEY)
-  const BRANCH_KEY_NAME := UTF8.EncodeAscii(BRANCH_KEY)
+
+  const KEY_ATTR_NAME : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2d, 0x61, 0x74, 0x74, 0x72, 0x2e, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x4b, 0x65, 0x79];
+    assert s == UTF8.EncodeAscii(RESERVED_PREFIX + BRANCH_KEY);
+    s
+
+  const BRANCH_KEY_NAME : UTF8.ValidUTF8Bytes :=
+    var s := [0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x4b, 0x65, 0x79];
+    assert s == UTF8.EncodeAscii(BRANCH_KEY);
+    s
 
   method {:test} {:vcs_split_on_every_assert} TestHappyCase()
   {
diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbGetEncryptedDataKeyDescriptionTest.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbGetEncryptedDataKeyDescriptionTest.dfy
index 2f5cb02a8..91633e3db 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbGetEncryptedDataKeyDescriptionTest.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbGetEncryptedDataKeyDescriptionTest.dfy
@@ -14,20 +14,55 @@ module DynamoDbGetEncryptedDataKeyDescriptionTest {
   import EdkWrapping
   import AlgorithmSuites
 
+  const abc : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x62, 0x63];
+    assert s == UTF8.EncodeAscii("abc");
+    s
+
+  const def : UTF8.ValidUTF8Bytes :=
+    var s := [0x64, 0x65, 0x66];
+    assert s == UTF8.EncodeAscii("def");
+    s
+
+  const awskms : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x6b, 0x6d, 0x73];
+    assert s == UTF8.EncodeAscii("aws-kms");
+    s
+
+  const keyproviderInfo : UTF8.ValidUTF8Bytes :=
+    var s := [0x6b, 0x65, 0x79, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f];
+    assert s == UTF8.EncodeAscii("keyproviderInfo");
+    s
+
+  const aws_kms_hierarchy : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x6b, 0x6d, 0x73, 0x2d, 0x68, 0x69, 0x65, 0x72, 0x61, 0x72, 0x63, 0x68, 0x79];
+    assert s == UTF8.EncodeAscii("aws-kms-hierarchy");
+    s
+
+  const raw_rsa : UTF8.ValidUTF8Bytes :=
+    var s := [0x72, 0x61, 0x77, 0x2d, 0x72, 0x73, 0x61];
+    assert s == UTF8.EncodeAscii("raw-rsa");
+    s
+
+  const aws_kms_rsa : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x6b, 0x6d, 0x73, 0x2d, 0x72, 0x73, 0x61];
+    assert s == UTF8.EncodeAscii("aws-kms-rsa");
+    s
+
   // THIS IS A TESTING RESOURCE DO NOT USE IN A PRODUCTION ENVIRONMENT
   const testVersion : Version := 1
   const testFlavor0 : Flavor := 0
   const testFlavor1 : Flavor := 1
   const testMsgID : MessageID := [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]
   const testLegend : Legend := [0x65, 0x73]
-  const testEncContext : CMPEncryptionContext := map[EncodeAscii("abc") := EncodeAscii("def")]
+  const testEncContext : CMPEncryptionContext := map[abc := def]
   const testAwsKmsDataKey := CMP.EncryptedDataKey(
-                               keyProviderId := EncodeAscii("aws-kms") ,
-                               keyProviderInfo := EncodeAscii("keyproviderInfo"),
+                               keyProviderId := awskms ,
+                               keyProviderInfo := keyproviderInfo,
                                ciphertext := [1, 2, 3, 4, 5])
   const testAwsKmsHDataKey := CMP.EncryptedDataKey(
-                                keyProviderId := EncodeAscii("aws-kms-hierarchy") ,
-                                keyProviderInfo := EncodeAscii("keyproviderInfo"),
+                                keyProviderId := aws_kms_hierarchy,
+                                keyProviderInfo := keyproviderInfo,
                                 ciphertext := [
                                   64, 92, 115, 7, 85, 121, 112, 79, 69, 12, 82, 25, 67, 34,
                                   11, 66, 93, 45, 40, 23, 90, 61, 16, 28, 59, 114, 52, 122,
@@ -40,12 +75,12 @@ module DynamoDbGetEncryptedDataKeyDescriptionTest {
                                   114, 76, 18, 103, 84, 34, 123, 1, 125, 61, 33, 13, 18, 81,
                                   24, 53, 53, 26, 60, 52, 85, 81, 96, 85, 72])
   const testRawRsaDataKey := CMP.EncryptedDataKey(
-                               keyProviderId := EncodeAscii("raw-rsa") ,
+                               keyProviderId := raw_rsa,
                                keyProviderInfo := [1, 2, 3, 4, 5],
                                ciphertext := [6, 7, 8, 9])
   const testAwsKmsRsaDataKey := CMP.EncryptedDataKey(
-                                  keyProviderId := EncodeAscii("aws-kms-rsa") ,
-                                  keyProviderInfo := EncodeAscii("keyproviderInfo"),
+                                  keyProviderId := aws_kms_rsa,
+                                  keyProviderInfo := keyproviderInfo,
                                   ciphertext := [1, 2, 3, 4, 5])
 
   method CreatePartialHeader(version : Version, flavor : Flavor, msgID : MessageID, legend : Legend, encContext : CMPEncryptionContext, dataKeyList : CMPEncryptedDataKeyList)
diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/DynamoDbMiddlewareSupport.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/DynamoDbMiddlewareSupport.dfy
index 8d2ce7e19..ab5524224 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/DynamoDbMiddlewareSupport.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/DynamoDbMiddlewareSupport.dfy
@@ -121,7 +121,10 @@ module DynamoDbMiddlewareSupport {
     .MapFailure(e => AwsCryptographyDbEncryptionSdkDynamoDb(e))
   }
 
-  const HierarchicalKeyringId := UTF8.EncodeAscii("aws-kms-hierarchy")
+  const HierarchicalKeyringId : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x6b, 0x6d, 0x73, 0x2d, 0x68, 0x69, 0x65, 0x72, 0x61, 0x72, 0x63, 0x68, 0x79];
+    assert s == UTF8.EncodeAscii("aws-kms-hierarchy");
+    s
 
   // Return Beacon Key ID for use in beaconizing records to be written
   function method GetKeyIdFromHeader(config : ValidTableConfig, output : AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes.EncryptItemOutput) :
diff --git a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Util.dfy b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Util.dfy
index 1c1233d71..08ba1fef9 100644
--- a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Util.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Util.dfy
@@ -35,9 +35,20 @@ module DynamoDbItemEncryptorUtil {
     x < y
   }
 
-  const TABLE_NAME : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-table-name")
-  const PARTITION_NAME : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-partition-name")
-  const SORT_NAME : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-sort-name")
+  const TABLE_NAME : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2d, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2d, 0x6e, 0x61, 0x6d, 0x65];
+    assert s == UTF8.EncodeAscii("aws-crypto-table-name");
+    s
+
+  const PARTITION_NAME : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2d, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6e, 0x61, 0x6d, 0x65];
+    assert s == UTF8.EncodeAscii("aws-crypto-partition-name");
+    s
+
+  const SORT_NAME : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2d, 0x73, 0x6f, 0x72, 0x74, 0x2d, 0x6e, 0x61, 0x6d, 0x65];
+    assert s == UTF8.EncodeAscii("aws-crypto-sort-name");
+    s
 
   const SELECTOR_TABLE_NAME : DDB.AttributeName := "aws_dbe_table_name"
   const SELECTOR_PARTITION_NAME : DDB.AttributeName := "aws_dbe_partition_name"
diff --git a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/test/DynamoDBItemEncryptorTest.dfy b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/test/DynamoDBItemEncryptorTest.dfy
index aa4dfaa65..6ec3f8a4c 100644
--- a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/test/DynamoDBItemEncryptorTest.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/test/DynamoDBItemEncryptorTest.dfy
@@ -25,7 +25,10 @@ module DynamoDbItemEncryptorTest {
   // encrypt => encrypted fields changed, others did not
   // various errors
 
-  const PublicKeyUtf8 : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii("aws-crypto-public-key")
+  const PublicKeyUtf8 : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79];
+    assert s == UTF8.EncodeAscii("aws-crypto-public-key");
+    s
 
   function method DDBS(x : string) : DDB.AttributeValue {
     DDB.AttributeValue.S(x)
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Crypt.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Crypt.dfy
index f8c178764..54609b0ec 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Crypt.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Crypt.dfy
@@ -49,6 +49,11 @@ module StructuredEncryptionCrypt {
     keyR.MapFailure(e => AwsCryptographyPrimitives(e))
   }
 
+  const AwsDbeField : UTF8.ValidUTF8Bytes :=
+    var s := [0x41, 0x77, 0x73, 0x44, 0x62, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64];
+    assert s == UTF8.EncodeAscii("AwsDbeField");
+    s
+
   function method FieldKeyNonce(offset : uint32)
     : (ret : Bytes)
     ensures |ret| == 16 // NOT NonceSize
@@ -61,17 +66,24 @@ module StructuredEncryptionCrypt {
     //# | 0x2c          | 1        | 44, the length of the eventual FieldKey |
     //# | offset        | 4        | 32 bit integer representation of offset |
     ensures ret ==
-            UTF8.EncodeAscii("AwsDbeField")
+            AwsDbeField
             + [(KeySize+NonceSize) as uint8]
             + UInt32ToSeq(offset)
   {
-    UTF8.EncodeAscii("AwsDbeField")
+    AwsDbeField
     + [(KeySize+NonceSize) as uint8] // length
     + UInt32ToSeq(offset)
   }
 
-  const LABEL_COMMITMENT_KEY := UTF8.EncodeAscii("AWS_DBE_COMMIT_KEY")
-  const LABEL_ENCRYPTION_KEY := UTF8.EncodeAscii("AWS_DBE_DERIVE_KEY")
+  const LABEL_COMMITMENT_KEY : UTF8.ValidUTF8Bytes :=
+    var s := [0x41, 0x57, 0x53, 0x5f, 0x44, 0x42, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x5f, 0x4b, 0x45, 0x59];
+    assert s == UTF8.EncodeAscii("AWS_DBE_COMMIT_KEY");
+    s
+
+  const LABEL_ENCRYPTION_KEY : UTF8.ValidUTF8Bytes :=
+    var s := [0x41, 0x57, 0x53, 0x5f, 0x44, 0x42, 0x45, 0x5f, 0x44, 0x45, 0x52, 0x49, 0x56, 0x45, 0x5f, 0x4b, 0x45, 0x59];
+    assert s == UTF8.EncodeAscii("AWS_DBE_DERIVE_KEY");
+    s
 
   // suitable for header field
   method GetCommitKey(
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Footer.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Footer.dfy
index 6c7f537ee..9239ad4cb 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Footer.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Footer.dfy
@@ -140,16 +140,28 @@ module StructuredEncryptionFooter {
     }
   }
 
+  const ENCRYPTED : UTF8.ValidUTF8Bytes :=
+    var s := [0x45, 0x4e, 0x43, 0x52, 0x59, 0x50, 0x54, 0x45, 0x44];
+    assert s == UTF8.EncodeAscii("ENCRYPTED");
+    s
+
+  const PLAINTEXT : UTF8.ValidUTF8Bytes :=
+    var s := [0x50, 0x4c, 0x41, 0x49, 0x4e, 0x54, 0x45, 0x58, 0x54];
+    assert s == UTF8.EncodeAscii("PLAINTEXT");
+    s
+
+
+
   // Given a StructuredDataTerminal, return the canonical value for the type, for use in the footer checksum calculations
   function method GetCanonicalType(value : StructuredDataTerminal, isEncrypted : bool)
     : Result<Bytes, Error>
   {
     if isEncrypted then
       :- Need(2 <= |value.value| < UINT64_LIMIT, E("Bad length."));
-      Success(UInt64ToSeq((|value.value| - 2) as uint64) + UTF8.EncodeAscii("ENCRYPTED"))
+      Success(UInt64ToSeq((|value.value| - 2) as uint64) + ENCRYPTED)
     else
       :- Need(|value.value| < UINT64_LIMIT, E("Bad length."));
-      Success(UInt64ToSeq((|value.value|) as uint64) + UTF8.EncodeAscii("PLAINTEXT") + value.typeId)
+      Success(UInt64ToSeq((|value.value|) as uint64) + PLAINTEXT + value.typeId)
   }
 
   function method GetCanonicalEncryptedField(fieldName : CanonicalPath, value : StructuredDataTerminal)
@@ -169,14 +181,14 @@ module StructuredEncryptionFooter {
               && ret.value ==
                  fieldName
                  + UInt64ToSeq((|value.value| - 2) as uint64)
-                 + UTF8.EncodeAscii("ENCRYPTED")
+                 + ENCRYPTED
                  + value.value // this is 2 bytes of unencrypted type, followed by encrypted value
   {
     :- Need(2 <= |value.value| < UINT64_LIMIT, E("Bad length."));
     Success(
       fieldName
       + UInt64ToSeq((|value.value| - 2) as uint64)
-      + UTF8.EncodeAscii("ENCRYPTED")
+      + ENCRYPTED
       + value.value
     )
   }
@@ -198,7 +210,7 @@ module StructuredEncryptionFooter {
               && ret.value ==
                  fieldName
                  + UInt64ToSeq((|value.value|) as uint64)
-                 + UTF8.EncodeAscii("PLAINTEXT")
+                 + PLAINTEXT
                  + value.typeId
                  + value.value
   {
@@ -206,7 +218,7 @@ module StructuredEncryptionFooter {
     Success(
       fieldName
       + UInt64ToSeq((|value.value|) as uint64)
-      + UTF8.EncodeAscii("PLAINTEXT")
+      + PLAINTEXT
       + value.typeId
       + value.value
     )
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Util.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Util.dfy
index fecdcad0a..93e36cd56 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Util.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Util.dfy
@@ -26,23 +26,48 @@ module StructuredEncryptionUtil {
   const FooterPath : Path := [member(StructureSegment(key := FooterField))]
   const HeaderPaths : seq<Path> := [HeaderPath, FooterPath]
   const ReservedCryptoContextPrefixString := "aws-crypto-"
-  const ReservedCryptoContextPrefixUTF8 := UTF8.EncodeAscii(ReservedCryptoContextPrefixString)
+
+  const ReservedCryptoContextPrefixUTF8 : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2d];
+    assert s == UTF8.EncodeAscii(ReservedCryptoContextPrefixString);
+    s
 
   const ATTR_PREFIX := ReservedCryptoContextPrefixString + "attr."
-  const EC_ATTR_PREFIX : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii(ATTR_PREFIX)
+
+  const EC_ATTR_PREFIX : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2d, 0x61, 0x74, 0x74, 0x72, 0x2e];
+    assert s == UTF8.EncodeAscii(ATTR_PREFIX);
+    s
+
   const LEGEND := ReservedCryptoContextPrefixString + "legend"
-  const LEGEND_UTF8 : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii(LEGEND)
+
+  const LEGEND_UTF8 : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x77, 0x73, 0x2d, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2d, 0x6c, 0x65, 0x67, 0x65, 0x6e, 0x64];
+    assert s == UTF8.EncodeAscii(LEGEND);
+    s
+
   const LEGEND_STRING : char := 'S'
   const LEGEND_NUMBER : char := 'N'
   const LEGEND_LITERAL : char := 'L'
   const LEGEND_BINARY : char := 'B'
 
   const NULL_STR : string := "null"
-  const NULL_UTF8 : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii(NULL_STR)
+  const NULL_UTF8 : UTF8.ValidUTF8Bytes :=
+    var s := [0x6e, 0x75, 0x6c, 0x6c];
+    assert s == UTF8.EncodeAscii(NULL_STR);
+    s
+
   const TRUE_STR : string := "true"
-  const TRUE_UTF8 : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii(TRUE_STR)
+  const TRUE_UTF8 : UTF8.ValidUTF8Bytes :=
+    var s := [0x74, 0x72, 0x75, 0x65];
+    assert s == UTF8.EncodeAscii(TRUE_STR);
+    s
+
   const FALSE_STR : string := "false"
-  const FALSE_UTF8 : UTF8.ValidUTF8Bytes := UTF8.EncodeAscii(FALSE_STR)
+  const FALSE_UTF8 : UTF8.ValidUTF8Bytes :=
+    var s := [0x66, 0x61, 0x6c, 0x73, 0x65];
+    assert s == UTF8.EncodeAscii(FALSE_STR);
+    s
 
   datatype CanonCryptoItem = CanonCryptoItem (
     key : CanonicalPath,
@@ -290,9 +315,10 @@ module StructuredEncryptionUtil {
     //# where `typeId` is the attribute's [type ID](./ddb-attribute-serialization.md#type-id)
     //# and `serializedValue` is the attribute's value serialized according to
     //# [Attribute Value Serialization](./ddb-attribute-serialization.md#attribute-value-serialization).
-    ensures ret == UTF8.EncodeAscii(Base64.Encode(t.typeId + t.value))
+    ensures ret == UTF8.Encode(Base64.Encode(t.typeId + t.value)).value
   {
-    UTF8.EncodeAscii(Base64.Encode(t.typeId + t.value))
+    var base := Base64.Encode(t.typeId + t.value);
+    UTF8.Encode(base).value
   }
 
   function method DecodeTerminal(t : UTF8.ValidUTF8Bytes) : (ret : Result<StructuredDataTerminal, string>)
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/test/HappyCaseTests.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/test/HappyCaseTests.dfy
index 7c41f025c..b498fa212 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/test/HappyCaseTests.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/test/HappyCaseTests.dfy
@@ -13,12 +13,22 @@ module HappyCaseTests {
   import MaterialProviders
   import StructuredDataTestFixtures
 
+  const some : UTF8.ValidUTF8Bytes :=
+    var s := [0x73, 0x6f, 0x6d, 0x65];
+    assert s == UTF8.EncodeAscii("some");
+    s
+
+  const value : UTF8.ValidUTF8Bytes :=
+    var s := [0x76, 0x61, 0x6c, 0x75, 0x65];
+    assert s == UTF8.EncodeAscii("value");
+    s
+
   method {:test} TestRoundTrip() {
     var structuredEncryption :-
       expect StructuredEncryption.StructuredEncryption(StructuredEncryption.DefaultStructuredEncryptionConfig());
     var cmm := StructuredDataTestFixtures.GetDefaultCMMWithKMSKeyring();
 
-    var encContext := map[UTF8.EncodeAscii("some") := UTF8.EncodeAscii("value")];
+    var encContext := map[some := value];
     var algSuiteId := AlgorithmSuites.DBE_ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384_SYMSIG_HMAC_SHA384.id.DBE;
 
     assert  && structuredEncryption.ValidState();
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/test/Header.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/test/Header.dfy
index be8905c86..fbd200499 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/test/Header.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/test/Header.dfy
@@ -21,15 +21,41 @@ module TestHeader {
   import AlgorithmSuites
   import Canonize
 
+  const abc : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x62, 0x63];
+    assert s == UTF8.EncodeAscii("abc");
+    s
+
+  const def : UTF8.ValidUTF8Bytes :=
+    var s := [0x64, 0x65, 0x66];
+    assert s == UTF8.EncodeAscii("def");
+    s
+
+  const cba : UTF8.ValidUTF8Bytes :=
+    var s := [0x63, 0x62, 0x61];
+    assert s == UTF8.EncodeAscii("cba");
+    s
+
+  const fed : UTF8.ValidUTF8Bytes :=
+    var s := [0x66, 0x65, 0x64];
+    assert s == UTF8.EncodeAscii("fed");
+    s
+
+  const provID : UTF8.ValidUTF8Bytes :=
+    var s := [0x70, 0x72, 0x6f, 0x76, 0x49, 0x44];
+    assert s == UTF8.EncodeAscii("provID");
+    s
+
+
   method {:test} TestRoundTrip() {
     var head := PartialHeader (
       version := 1,
       flavor := 1,
       msgID := [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],
       legend := [0x65, 0x73],
-      encContext := map[EncodeAscii("abc") := EncodeAscii("def")],
+      encContext := map[abc := def],
       dataKeys := [CMP.EncryptedDataKey(
-                     keyProviderId := EncodeAscii("provID") ,
+                     keyProviderId := provID,
                      keyProviderInfo := [1,2,3,4,5],
                      ciphertext := [6,7,8,9])]
     );
@@ -45,9 +71,9 @@ module TestHeader {
       flavor := 1,
       msgID := [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],
       legend := [0x65, 0x73],
-      encContext := map[EncodeAscii("abc") := EncodeAscii("def")],
+      encContext := map[abc := def],
       dataKeys := [CMP.EncryptedDataKey(
-                     keyProviderId := EncodeAscii("provID") ,
+                     keyProviderId := provID,
                      keyProviderInfo := [1,2,3,4,5],
                      ciphertext := [6,7,8,9])]
     );
@@ -76,7 +102,7 @@ module TestHeader {
   const e : uint8 := 'e' as uint8
   const f : uint8 := 'f' as uint8
   method {:test} TestDuplicateContext() {
-    var cont : CMPEncryptionContext := map[EncodeAscii("abc") := EncodeAscii("def"), EncodeAscii("cba") := EncodeAscii("fed")];
+    var cont : CMPEncryptionContext := map[abc := def, cba := fed];
     var serCont := SerializeContext(cont);
     expect serCont == [
                         0,2, // two items
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/test/Paths.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/test/Paths.dfy
index 63726a18d..ce3c1704b 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/test/Paths.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/test/Paths.dfy
@@ -9,31 +9,25 @@ module PathsTests {
   import opened StructuredEncryptionUtil
   import opened StructuredEncryptionPaths
 
+  const example_table : UTF8.ValidUTF8Bytes :=
+    var s := [0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65];
+    assert s == UTF8.EncodeAscii("example_table");
+    s
+
+  const name : UTF8.ValidUTF8Bytes :=
+    var s := [0x6e, 0x61, 0x6d, 0x65];
+    assert s == UTF8.EncodeAscii("name");
+    s
+
   method {:test} TestSpecExamples() {
     var tableName : GoodString := "example_table";
     assert(ValidString("example_table"));
     var pathToTest := StringToUniPath("name");
     expect CanonPath(tableName, pathToTest) ==
-           UTF8.EncodeAscii("example_table")
+           example_table
            + [0,0,0,0,0,0,0,1] // depth
            + ['$' as uint8] // map
            + [0,0,0,0,0,0,0,4] // length
-           + UTF8.EncodeAscii("name");
-
-    // var history := Selector.Map("status-history");
-    // var index := Selector.List(0);
-    // var timestamp := Selector.Map("timestamp");
-    // var pathToTest2 := TerminalLocation([history, index, timestamp]);
-    // expect CanonPath(tableName, pathToTest2) ==
-    //        UTF8.EncodeAscii("example_table")
-    //        + [0,0,0,0,0,0,0,3] // depth
-    //        + ['$' as uint8] // map
-    //        + [0,0,0,0,0,0,0,14] // length of "status-history"
-    //        + UTF8.EncodeAscii("status-history")
-    //        + ['#' as uint8] // array
-    //        + [0,0,0,0,0,0,0,0] // index
-    //        + ['$' as uint8] // map
-    //        + [0,0,0,0,0,0,0,9] // length of "timestamp"
-    //        + UTF8.EncodeAscii("timestamp");
+           + name;
   }
 }
diff --git a/TestVectors/dafny/DDBEncryption/src/JsonConfig.dfy b/TestVectors/dafny/DDBEncryption/src/JsonConfig.dfy
index 46605a425..b39125129 100644
--- a/TestVectors/dafny/DDBEncryption/src/JsonConfig.dfy
+++ b/TestVectors/dafny/DDBEncryption/src/JsonConfig.dfy
@@ -33,6 +33,11 @@ module {:options "-functionSyntax:4"} JsonConfig {
   import DynamoDbItemEncryptor
 
 
+  const abc : UTF8.ValidUTF8Bytes :=
+    var s := [0x61, 0x62, 0x63];
+    assert s == UTF8.EncodeAscii("abc");
+    s
+
   const DEFAULT_KEYS : string := "../../../submodules/MaterialProviders/TestVectorsAwsCryptographicMaterialProviders/dafny/TestVectorsAwsCryptographicMaterialProviders/test/keys.json"
 
   predicate IsValidInt32(x: int)  { -0x8000_0000 <= x < 0x8000_0000}
@@ -483,7 +488,7 @@ module {:options "-functionSyntax:4"} JsonConfig {
 
     :- Need(|standardBeacons| > 0, "A Search Config needs at least one standard beacon.");
     var keyMaterial : KeyMaterial.KeyMaterial :=
-      KeyMaterial.StaticKeyStoreInformation("abc", UTF8.EncodeAscii("abc"), [1,2,3,4,5], [1,2,3,4,5]);
+      KeyMaterial.StaticKeyStoreInformation("abc", abc, [1,2,3,4,5], [1,2,3,4,5]);
     var store := SKS.CreateStaticKeyStore(keyMaterial);
     var source : Types.BeaconKeySource :=
       if keySource.Some? then
@@ -508,7 +513,7 @@ module {:options "-functionSyntax:4"} JsonConfig {
     ensures output.Success? ==> output.value.ValidState() && fresh(output.value.Modifies())
   {
     var keyMaterial : KeyMaterial.KeyMaterial :=
-      KeyMaterial.StaticKeyStoreInformation("abc", UTF8.EncodeAscii("abc"), [1,2,3,4,5], [1,2,3,4,5]);
+      KeyMaterial.StaticKeyStoreInformation("abc", abc, [1,2,3,4,5], [1,2,3,4,5]);
     var store := SKS.CreateStaticKeyStore(keyMaterial);
     var source : Types.BeaconKeySource := Types.single(Types.SingleKeyStore(keyId := "foo", cacheTTL := 42));
 
diff --git a/submodules/MaterialProviders b/submodules/MaterialProviders
index 5808fbfd1..786404f1d 160000
--- a/submodules/MaterialProviders
+++ b/submodules/MaterialProviders
@@ -1 +1 @@
-Subproject commit 5808fbfd13ed12eb258883010626e1ae75708089
+Subproject commit 786404f1d1bdf86814c5018ce0b3b1149ef65b3e

From bd9d7bef24c10c95a8ceb9efe8c2d23636c5d76f Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Thu, 6 Feb 2025 11:07:51 -0800
Subject: [PATCH 08/26] m

---
 ...ptionSdkStructuredEncryptionOperations.dfy | 13 ++++-
 .../dafny/StructuredEncryption/src/Footer.dfy | 50 ++++++++++++++++---
 2 files changed, 54 insertions(+), 9 deletions(-)

diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations.dfy
index e2aef0b72..fffc07f73 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations.dfy
@@ -127,7 +127,7 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst
   }
 
   // Return the sum of the sizes of the given fields
-  function method {:opaque} SumValueSize(fields : CanonCryptoList)
+  function {:opaque}  SumValueSize(fields : CanonCryptoList)
     : nat
   {
     if |fields| == 0 then
@@ -136,6 +136,17 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst
       |fields[0].data.value| + SumValueSize(fields[1..])
     else
       SumValueSize(fields[1..])
+  } by method {
+    reveal SumValueSize();
+    var sum : nat := 0;
+    for i := |fields| downto 0
+      invariant sum == SumValueSize(fields[i..])
+    {
+      if fields[i].action == ENCRYPT_AND_SIGN {
+        sum := |fields[i].data.value| + sum;
+      }
+    }
+    return sum;
   }
 
   function method {:opaque} GetAlgorithmSuiteId(alg : Option<CMP.DBEAlgorithmSuiteId>)
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Footer.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Footer.dfy
index 9239ad4cb..e994c382e 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Footer.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Footer.dfy
@@ -234,20 +234,46 @@ module StructuredEncryptionFooter {
       GetCanonicalPlaintextField(data.key, data.data)
   }
 
-  function method CanonContent (
-    data : CanonCryptoList,      // remaining fields to be canonized
-    canonized : Bytes := []   // output
-  ) : Result<Bytes, Error>
+  function CanonContent (data : CanonCryptoList)
+    : Result<Bytes, Error>
   {
     if |data| == 0 then
-      Success(canonized)
+      Success([])
     else if data[0].action == DO_NOTHING then
-      CanonContent(data[1..], canonized)
+      CanonContent(data[1..])
     else
+      var tail :- CanonContent(data[1..]);
       var newPart :- GetCanonicalItem(data[0]);
-      CanonContent(data[1..], canonized + newPart)
+      Success(newPart + tail)
+  } by method {
+    var i: nat := |data|;
+    var vectors : Bytes := [];
+
+    while i != 0
+      decreases i
+      invariant Success(vectors) == CanonContent(data[i..])
+    {
+      i := i - 1;
+      if data[i].action != DO_NOTHING {
+        var test := GetCanonicalItem(data[i]);
+        if test.Failure? {
+          ghost var j := i;
+          while j != 0
+            decreases j
+            invariant Failure(test.error) == CanonContent(data[j..])
+          {
+            j := j - 1;
+          }
+          return Failure(test.error);
+        }
+        vectors := test.value + vectors;
+      }
+    }
+
+    return Success(vectors);
   }
 
+
   function method CanonRecord (
     data : CanonCryptoList,
     header : Bytes,
@@ -377,13 +403,21 @@ module StructuredEncryptionFooter {
     }
   }
 
-  function method SerializeTags(tags : seq<RecipientTag>)
+  function SerializeTags(tags : seq<RecipientTag>)
     : Bytes
   {
     if |tags| == 0 then
       []
     else
       tags[0] + SerializeTags(tags[1..])
+  } by method {
+    var result : Bytes := [];
+    for i := |tags| downto 0
+      invariant result == SerializeTags(tags[i..])
+    {
+      result := tags[i] + result;
+    }
+    return result;
   }
 
   function method SerializeSig(sig : Option<Signature>)

From bbebafc4864d866f070fca0cb52013b96fd396bd Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Sun, 9 Feb 2025 13:42:20 -0500
Subject: [PATCH 09/26] m

---
 .../src/Index.dfy                             |  24 +-
 .../dafny/DynamoDbItemEncryptor/src/Util.dfy  |  19 +-
 ...ptionSdkStructuredEncryptionOperations.dfy | 134 +++++++----
 .../StructuredEncryption/src/Canonize.dfy     |  75 ++++---
 .../dafny/StructuredEncryption/src/Header.dfy | 209 ++++++++++--------
 .../dafny/StructuredEncryption/src/Paths.dfy  |  29 ++-
 .../dafny/StructuredEncryption/src/Util.dfy   |  10 +-
 .../DDBEncryption/src/DecryptManifest.dfy     |   2 +
 submodules/MaterialProviders                  |   2 +-
 submodules/smithy-dafny                       |   2 +-
 10 files changed, 319 insertions(+), 187 deletions(-)

diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/Index.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/Index.dfy
index 77f3b7aab..4d2ca0cbc 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/Index.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryptionTransforms/src/Index.dfy
@@ -58,13 +58,19 @@ module
     requires tableName in config.tableEncryptionConfigs
     ensures SearchModifies(config, tableName) <= TheModifies(config)
 
-  function method {:tailrecursion} AddSignedBeaconActions(names : seq<string>, actions : ET.AttributeActions) : ET.AttributeActions
+  function method {:tailrecursion} AddSignedBeaconActions(
+    names : seq<string>,
+    actions : ET.AttributeActions,
+    pos : nat := 0
+  ) : ET.AttributeActions
     requires forall k <- names :: DDB.IsValid_AttributeName(k)
+    requires pos <= |names|
+    decreases |names| - pos
   {
-    if |names| == 0 then
+    if |names| == pos then
       actions
     else
-      AddSignedBeaconActions(names[1..], actions[names[0] := SET.SIGN_ONLY])
+      AddSignedBeaconActions(names, actions[names[0] := SET.SIGN_ONLY], pos+1)
   }
 
   predicate method IsConfigured(config : AwsCryptographyDbEncryptionSdkDynamoDbTypes.DynamoDbTableEncryptionConfig, name : string)
@@ -79,11 +85,11 @@ module
   {
     forall tableName <- configs :: ValidTableConfig?(configs[tableName])
   }
-  predicate {:opaque} CorrectlyTransferedStructure?(
+  predicate {:opaque} CorrectlyTransferredStructure?(
     internalConfigs: map<string, DdbMiddlewareConfig.ValidTableConfig>,
     config: AwsCryptographyDbEncryptionSdkDynamoDbTypes.DynamoDbTablesEncryptionConfig
   )
-    ensures 0 == |internalConfigs| ==> CorrectlyTransferedStructure?(internalConfigs, config)
+    ensures 0 == |internalConfigs| ==> CorrectlyTransferredStructure?(internalConfigs, config)
   {
     forall tableName <- internalConfigs
       ::
@@ -142,7 +148,7 @@ module
       invariant forall k <- m' :: m'[k] == config.tableEncryptionConfigs[k]
       invariant forall internalConfig <- internalConfigs.Values :: internalConfig.logicalTableName in allLogicalTableNames
 
-      invariant CorrectlyTransferedStructure?(internalConfigs, config)
+      invariant CorrectlyTransferredStructure?(internalConfigs, config)
       invariant AllTableConfigsValid?(internalConfigs)
       invariant ValidConfig?(Config(internalConfigs))
 
@@ -223,10 +229,10 @@ module
         assert internalConfig.physicalTableName == tableName;
       }
 
-      assert CorrectlyTransferedStructure?(internalConfigs, config) by {
-        reveal CorrectlyTransferedStructure?();
+      assert CorrectlyTransferredStructure?(internalConfigs, config) by {
+        reveal CorrectlyTransferredStructure?();
         reveal ConfigsMatch();
-        assert CorrectlyTransferedStructure?(internalConfigs - {tableName}, config);
+        assert CorrectlyTransferredStructure?(internalConfigs - {tableName}, config);
         assert ConfigsMatch(tableName, internalConfig, inputConfig);
       }
 
diff --git a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Util.dfy b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Util.dfy
index 08ba1fef9..7bea3d88b 100644
--- a/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Util.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbItemEncryptor/src/Util.dfy
@@ -191,25 +191,30 @@ module DynamoDbItemEncryptorUtil {
     keys : seq<UTF8.ValidUTF8Bytes>,
     context : MPL.EncryptionContext,
     legend : string,
+    keyPos : nat := 0,
+    legendPos : nat := 0,
     attrMap : DDB.AttributeMap := map[]
   )
     : Result<DDB.AttributeMap, string>
     requires forall k <- keys :: k in context
+    requires keyPos <= |keys|
+    requires legendPos <= |legend|
+    decreases |keys| - keyPos
   {
-    if |keys| == 0 then
-      if |legend| == 0 then
+    if |keys| == keyPos then
+      if |legend| == legendPos then
         Success(attrMap)
       else
         Failure("Encryption Context Legend is too long.")
     else
-      var key : UTF8.ValidUTF8Bytes := keys[0];
+      var key : UTF8.ValidUTF8Bytes := keys[keyPos];
       if SE.EC_ATTR_PREFIX < key then
-        :- Need(0 < |legend|, "Encryption Context Legend is too short.");
+        :- Need(legendPos < |legend|, "Encryption Context Legend is too short.");
         var attrName :- GetAttributeName(key);
-        var attrValue :- GetAttrValue(context[key], legend[0]);
-        GetV2AttrMap(keys[1..], context, legend[1..], attrMap[attrName := attrValue])
+        var attrValue :- GetAttrValue(context[key], legend[legendPos]);
+        GetV2AttrMap(keys, context, legend, keyPos+1, legendPos+1, attrMap[attrName := attrValue])
       else
-        GetV2AttrMap(keys[1..], context, legend, attrMap)
+        GetV2AttrMap(keys, context, legend, keyPos+1, legendPos, attrMap)
   }
 
   function method GetAttributeName(ddbAttrKey: UTF8.ValidUTF8Bytes)
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations.dfy
index fffc07f73..a5204fc19 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations.dfy
@@ -114,16 +114,16 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst
   function method {:opaque} GetBinary(data : AuthList, path : Path): (result: Result<StructuredDataTerminal, Error>)
     ensures result.Success? ==> exists x :: x in data && x.key == path
   {
-    var data := FindAuth(data, path);
+    var pos := FindAuth(data, path);
 
-    if data.None? then
+    if pos.None? then
       Failure(E("The field name " + Paths.PathToString(path) + " is required."))
-    else if data.value.data.typeId != BYTES_TYPE_ID then
+    else if data[pos.value].data.typeId != BYTES_TYPE_ID then
       Failure(E(Paths.PathToString(path) + " must be a binary Terminal."))
-    else if data.value.action != DO_NOT_SIGN then
+    else if data[pos.value].action != DO_NOT_SIGN then
       Failure(E(Paths.PathToString(path) + " must be DO_NOT_SIGN."))
     else
-      Success(data.value.data)
+      Success(data[pos.value].data)
   }
 
   // Return the sum of the sizes of the given fields
@@ -269,28 +269,66 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst
     output := GetV2EncryptionContext2(contextAttrs);
   }
 
-  function method {:opaque} Find(haystack : CryptoList, needle : Path) : Result<CryptoItem, Error>
+  function {:opaque} Find(haystack : CryptoList, needle : Path, start : nat := 0) : (res : Result<nat, Error>)
+    requires start <= |haystack|
+    requires forall i | 0 <= i < start :: haystack[i].key != needle
+    ensures (exists x <- haystack :: x.key == needle) <==> res.Success?
+    ensures (forall x <- haystack :: x.key != needle) <==> res.Failure?
+    ensures (forall x <- haystack :: x.key != needle) <==> res == Failure(E("Not Found"))
+    ensures res.Success? ==>
+              && 0 <= res.value < |haystack|
+              && haystack[res.value].key == needle
+              && (forall i | 0 <= i < res.value :: haystack[i].key != needle)
+    decreases |haystack| - start
   {
-    if |haystack| == 0 then
+    if |haystack| == start then
       Failure(E("Not Found"))
-    else if haystack[0].key == needle
-    then Success(haystack[0])
+    else if haystack[start].key == needle then
+      Success(start)
     else
-      Find(haystack[1..], needle)
+      Find(haystack, needle, start + 1)
+  }
+  by method {
+    for i := 0 to |haystack|
+      invariant forall x <- haystack[..i] :: x.key != needle
+    {
+      if haystack[i].key == needle {
+        return Success(i);
+      }
+    }
+    return Failure(E("Not Found"));
   }
 
-  function method {:opaque} FindAuth(haystack : AuthList, needle : Path) : (result : Option<AuthItem>)
-    ensures result.Some? ==> exists x :: x in haystack && x.key == needle
+  function {:opaque} FindAuth(haystack : AuthList, needle : Path, start : nat := 0) : (res : Option<nat>)
+    requires start <= |haystack|
+    requires forall i | 0 <= i < start :: haystack[i].key != needle
+    ensures (exists x <- haystack :: x.key == needle) <==> res.Some?
+    ensures (forall x <- haystack :: x.key != needle) <==> res == None
+    ensures res.Some? ==>
+              && 0 <= res.value < |haystack|
+              && haystack[res.value].key == needle
+              && (forall i | 0 <= i < res.value :: haystack[i].key != needle)
+    decreases |haystack| - start
   {
-    if |haystack| == 0 then
+    if |haystack| == start then
       None
-    else if haystack[0].key == needle
-    then Some(haystack[0])
+    else if haystack[start].key == needle then
+      Some(start)
     else
-      FindAuth(haystack[1..], needle)
+      FindAuth(haystack, needle, start + 1)
+  }
+  by method {
+    for i := 0 to |haystack|
+      invariant forall x <- haystack[..i] :: x.key != needle
+    {
+      if haystack[i].key == needle {
+        return Some(i);
+      }
+    }
+    return None;
   }
 
-  function method {:opaque} CountEncrypted(list : CanonCryptoList) : nat
+  function {:opaque} CountEncrypted(list : CanonCryptoList) : nat
   {
     if |list| == 0 then
       0
@@ -298,6 +336,17 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst
       1 + CountEncrypted(list[1..])
     else
       CountEncrypted(list[1..])
+  } by method {
+    reveal CountEncrypted();
+    var result : nat := 0;
+    for i := |list| downto 0
+      invariant result == CountEncrypted(list[i..])
+    {
+      if list[i].action == ENCRYPT_AND_SIGN {
+        result := result + 1;
+      }
+    }
+    return result;
   }
 
   method {:vcs_split_on_every_assert} GetV2EncryptionContext2(fields : CryptoList)
@@ -355,7 +404,7 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst
       var fieldUtf8 := keys[i];
       var fieldStr := fieldMap[fieldUtf8];
       var item :- Find(fields, fieldMap[fieldUtf8]);
-      var attr : StructuredDataTerminal := item.data;
+      var attr : StructuredDataTerminal := fields[item].data;
       var attrStr : ValidUTF8Bytes;
       var legendChar : char;
       if attr.typeId == NULL {
@@ -395,19 +444,18 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst
     keys : seq<string>,
     plaintextStructure: StructuredDataMap,
     cryptoSchema: CryptoSchemaMap,
-    ghost origKeys : seq<string> := keys,
+    pos : nat := 0,
     acc : CryptoList := []
   )
     : (ret : Result<CryptoList, Error>)
+    requires 0 <= pos <= |keys|
+    requires |acc| == pos
     requires forall k <- keys :: k in plaintextStructure
     requires forall k <- keys :: k in cryptoSchema
     requires forall k <- acc :: |k.key| == 1
-    requires CryptoListHasNoDuplicates(acc)
-    requires |acc| + |keys| == |origKeys|
-    requires keys == origKeys[|acc|..]
-    requires forall i | 0 <= i < |acc| :: acc[i].key == Paths.StringToUniPath(origKeys[i])
+    requires forall i | 0 <= i < |acc| :: acc[i].key == Paths.StringToUniPath(keys[i])
     requires Seq.HasNoDuplicates(keys)
-    requires Seq.HasNoDuplicates(origKeys)
+    decreases |keys| - pos
 
     ensures ret.Success? ==>
               && (forall k <- ret.value :: |k.key| == 1)
@@ -415,14 +463,14 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst
   {
     reveal Seq.HasNoDuplicates();
     Paths.StringToUniPathUnique();
-    if |keys| == 0 then
+    if |keys| == pos then
       Success(acc)
     else
-      var key := keys[0];
+      var key := keys[pos];
       var path := Paths.StringToUniPath(key);
       var item := CryptoItem(key := path, data := plaintextStructure[key], action := cryptoSchema[key]);
       var newAcc := acc + [item];
-      BuildCryptoMap2(keys[1..], plaintextStructure, cryptoSchema, origKeys, newAcc)
+      BuildCryptoMap2(keys, plaintextStructure, cryptoSchema, pos+1, newAcc)
   }
 
   function method BuildCryptoMap(plaintextStructure: StructuredDataMap, cryptoSchema: CryptoSchemaMap) :
@@ -440,33 +488,33 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst
     keys : seq<string>,
     plaintextStructure: StructuredDataMap,
     authSchema: AuthenticateSchemaMap,
-    ghost origKeys : seq<string> := keys,
+    pos : nat := 0,
     acc : AuthList := []
   )
     : (ret : Result<AuthList, Error>)
+    requires 0 <= pos <= |keys|
+    requires |acc| == pos
     requires forall k <- keys :: k in plaintextStructure
     requires forall k <- keys :: k in authSchema
     requires forall k <- acc :: |k.key| == 1
+    requires forall i | 0 <= i < |acc| :: acc[i].key == Paths.StringToUniPath(keys[i])
     requires AuthListHasNoDuplicates(acc)
-    requires |acc| + |keys| == |origKeys|
-    requires keys == origKeys[|acc|..]
-    requires forall i | 0 <= i < |acc| :: acc[i].key == Paths.StringToUniPath(origKeys[i])
     requires Seq.HasNoDuplicates(keys)
-    requires Seq.HasNoDuplicates(origKeys)
+    decreases |keys| - pos
 
     ensures ret.Success? ==>
               && (forall k <- ret.value :: |k.key| == 1)
               && AuthListHasNoDuplicates(ret.value)
   {
     reveal Seq.HasNoDuplicates();
-    if |keys| == 0 then
+    if |keys| == pos then
       Success(acc)
     else
-      var key := keys[0];
+      var key := keys[pos];
       var path := Paths.StringToUniPath(key);
       var item := AuthItem(key := path, data := plaintextStructure[key], action := authSchema[key]);
       var newAcc := acc + [item];
-      BuildAuthMap2(keys[1..], plaintextStructure, authSchema, origKeys, newAcc)
+      BuildAuthMap2(keys, plaintextStructure, authSchema, pos+1, newAcc)
   }
 
   function method BuildAuthMap(plaintextStructure: StructuredDataMap, authSchema: AuthenticateSchemaMap)
@@ -480,24 +528,28 @@ module AwsCryptographyDbEncryptionSdkStructuredEncryptionOperations refines Abst
     BuildAuthMap2(keys, plaintextStructure, authSchema)
   }
 
-  function method UnBuildCryptoMap(list : CryptoList, dataSoFar : StructuredDataMap := map[], actionsSoFar : CryptoSchemaMap := map[]) :
+  function method {:tailrecursion} UnBuildCryptoMap(list : CryptoList, pos : nat := 0, dataSoFar : StructuredDataMap := map[], actionsSoFar : CryptoSchemaMap := map[]) :
     (res : Result<(StructuredDataMap, CryptoSchemaMap), Error>)
+    requires 0 <= pos <= |list|
+    requires |dataSoFar| == pos
+    requires |actionsSoFar| <= pos
     requires forall k <- actionsSoFar :: k in dataSoFar
     requires (forall v :: v in actionsSoFar.Values ==> IsAuthAttr(v))
     requires forall k <- list :: |k.key| == 1
+    decreases |list| - pos
     ensures res.Success? ==>
               && (forall k <- res.value.1 :: k in res.value.0)
               && (forall v :: v in res.value.1.Values ==> IsAuthAttr(v))
   {
-    if |list| == 0 then
+    if |list| == pos then
       Success((dataSoFar, actionsSoFar))
     else
-      var key :- Paths.UniPathToString(list[0].key);
+      var key :- Paths.UniPathToString(list[pos].key);
       :- Need(key !in dataSoFar, E("Duplicate Key " + key));
-      if IsAuthAttr(list[0].action) then
-        UnBuildCryptoMap(list[1..], dataSoFar[key := list[0].data], actionsSoFar[key := list[0].action])
+      if IsAuthAttr(list[pos].action) then
+        UnBuildCryptoMap(list, pos+1, dataSoFar[key := list[pos].data], actionsSoFar[key := list[pos].action])
       else
-        UnBuildCryptoMap(list[1..], dataSoFar[key := list[0].data], actionsSoFar)
+        UnBuildCryptoMap(list, pos+1, dataSoFar[key := list[pos].data], actionsSoFar)
   }
 
   lemma EncryptStructureOutputHasSinglePaths(origData : CryptoList, finalData : CryptoList)
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy
index 306f63521..698dc237b 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy
@@ -139,25 +139,28 @@ module {:options "/functionSyntax:4" } Canonize {
   opaque function {:tailrecursion} ResolveLegend(
     fields : CanonAuthList,
     legend : Header.Legend,
-    ghost origFields : CanonAuthList,
-    acc : CanonCryptoList
+    pos : nat := 0,
+    legendPos : nat := 0,
+    acc : CanonCryptoList := []
   )
     : (ret : Result<CanonCryptoList, Error>)
-    requires |fields| + |acc| == |origFields|
-    requires forall i | 0 <= i < |acc| :: Same(origFields[i], acc[i])
-    requires forall i | |acc| <= i < |origFields| :: origFields[i] == fields[i-|acc|]
+    requires 0 <= pos <= |fields|
+    requires 0 <= legendPos <= |legend|
+    requires pos == |acc|
+    requires forall i | 0 <= i < pos :: Same(fields[i], acc[i])
     ensures ret.Success? ==>
-              && |origFields| == |ret.value|
-              && forall i | 0 <= i < |origFields| :: Same(origFields[i], ret.value[i])
+              && |fields| == |ret.value|
+              && forall i | 0 <= i < |fields| :: Same(fields[i], ret.value[i])
+    decreases |fields| - pos
   {
-    if |fields| == 0 then
-      :- Need(|legend| == 0, E("Schema changed : something that was signed is now unsigned."));
+    if |fields| == pos then
+      :- Need(|legend| == legendPos, E("Schema changed : something that was signed is now unsigned."));
       Success(acc)
-    else if fields[0].action == DO_NOT_SIGN then
-      ResolveLegend(fields[1..], legend, origFields, acc + [MakeCryptoItem(fields[0], DO_NOTHING)])
+    else if fields[pos].action == DO_NOT_SIGN then
+      ResolveLegend(fields, legend, pos+1, legendPos, acc + [MakeCryptoItem(fields[pos], DO_NOTHING)])
     else
-      :- Need(0 < |legend|, E("Schema changed : something that was unsigned is now signed."));
-      ResolveLegend(fields[1..], legend[1..], origFields, acc + [MakeCryptoItem(fields[0], LegendToAction(legend[0]))])
+      :- Need(legendPos < |legend|, E("Schema changed : something that was unsigned is now signed."));
+      ResolveLegend(fields, legend, pos+1, legendPos+1, acc + [MakeCryptoItem(fields[pos], LegendToAction(legend[legendPos]))])
   }
 
   opaque function ForEncrypt(tableName : GoodString, data : CryptoList)
@@ -562,7 +565,7 @@ module {:options "/functionSyntax:4" } Canonize {
     reveal CanonAuthMatchesAuthList();
     reveal CanonCryptoMatchesAuthList();
     reveal IsCryptoSorted();
-    var canonResolved :- ResolveLegend(canonSorted, legend, canonSorted, []);
+    var canonResolved :- ResolveLegend(canonSorted, legend);
 
     assert (forall k <- data :: AuthExistsInCanonCrypto(k, canonResolved)) by {
       reveal AuthExistsInCanonCrypto();
@@ -622,17 +625,19 @@ module {:options "/functionSyntax:4" } Canonize {
     && x.action == y.action
   }
 
-  function UnCanon(input : CanonCryptoList) : (ret : CryptoList)
-    ensures
-      && |ret| == |input|
-      && (forall i | 0 <= i < |input| :: SameUnCanon(input[i], ret[i]))
+  function {:tailrecursion} UnCanon(input : CanonCryptoList, pos : nat := 0, acc : CryptoList := []) : (ret : CryptoList)
+    requires 0 <= pos <= |input|
+    requires pos == |acc|
+    requires forall i | 0 <= i < |acc| :: SameUnCanon(input[i], acc[i])
+    ensures |ret| == |input|
+    ensures forall i | 0 <= i < |input| :: SameUnCanon(input[i], ret[i])
+    decreases |input| - pos
   {
-    if |input| == 0 then
-      []
+    if |input| == pos then
+      acc
     else
-      var newItem := CryptoItem(key := input[0].origKey, data := input[0].data, action := input[0].action);
-      assert SameUnCanon(input[0], newItem);
-      [newItem] + UnCanon(input[1..])
+      var newItem := CryptoItem(key := input[pos].origKey, data := input[pos].data, action := input[pos].action);
+      UnCanon(input, pos+1, acc + [newItem])
   }
 
   lemma Update2ImpliesUpdate3()
@@ -795,17 +800,33 @@ module {:options "/functionSyntax:4" } Canonize {
     && CryptoListHasNoDuplicates(finalData)
   }
 
-  opaque function RemoveHeaderPaths(xs : CryptoList) : (ret : CryptoList)
+  opaque function {:tailrecursion} RemoveHeaderPaths(xs : CryptoList, pos : nat := 0, acc : CryptoList := []) : (ret : CryptoList)
     requires CryptoListHasNoDuplicates(xs)
+    requires 0 <= pos <= |xs|
+    requires !exists x :: x in acc && x.key in [HeaderPath, FooterPath]
+    requires !exists x :: x in acc && x.key == HeaderPath
+    requires !exists x :: x in acc && x.key == FooterPath
+    requires forall x <- acc :: x in xs[..pos]
+    requires forall i | 0 <= i < pos :: (xs[i].key in [HeaderPath, FooterPath] || xs[i] in acc)
+    // should file a ticket, because this is FALSE but verifies
+    // requires forall x <- xs :: (x.key in [HeaderPath, FooterPath] || x in acc)
+    requires CryptoListHasNoDuplicates(acc)
+
     ensures !exists x :: x in ret && x.key in [HeaderPath, FooterPath]
     ensures !exists x :: x in ret && x.key == HeaderPath
     ensures !exists x :: x in ret && x.key == FooterPath
     ensures forall x <- ret :: x in xs
-    ensures forall x <- xs :: x.key in [HeaderPath, FooterPath] || x in ret
+    ensures forall x <- xs :: (x.key in [HeaderPath, FooterPath] || x in ret)
     ensures CryptoListHasNoDuplicates(ret)
+
+    decreases |xs| - pos
   {
-    if |xs| == 0 then []
-    else (if xs[0].key in [HeaderPath, FooterPath] then [] else [xs[0]]) + RemoveHeaderPaths(xs[1..])
+    if |xs| == pos then
+      acc
+    else if xs[pos].key in [HeaderPath, FooterPath] then
+      RemoveHeaderPaths(xs, pos+1, acc)
+    else
+      RemoveHeaderPaths(xs, pos+1, acc + [xs[pos]])
   }
 
   opaque function RemoveHeaders(input : CryptoList, ghost origData : AuthList)
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy
index 66169eea6..fd016868d 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy
@@ -387,18 +387,21 @@ module StructuredEncryptionHeader {
   // Create a Legend for the given attrs of the Schema
   function method {:tailrecursion} MakeLegend2(
     data : CanonCryptoList,
+    pos : nat := 0,
     serialized : Legend := EmptyLegend
   )
     : (ret : Result<Legend, Error>)
+    requires 0 <= pos <= |data|
+    decreases |data| - pos
   {
-    if |data| == 0 then
+    if |data| == pos then
       Success(serialized)
-    else if IsAuthAttr(data[0].action) then
+    else if IsAuthAttr(data[pos].action) then
       :- Need((|serialized| + 1) < UINT16_LIMIT, E("Legend Too Long."));
-      var legendChar := GetActionLegend(data[0].action);
-      MakeLegend2(data[1..], serialized + [legendChar])
+      var legendChar := GetActionLegend(data[pos].action);
+      MakeLegend2(data, pos+1, serialized + [legendChar])
     else
-      MakeLegend2(data[1..], serialized)
+      MakeLegend2(data, pos+1, serialized)
   }
 
   // CryptoAction to bytes. One byte for signed, zero bytes for unsigned
@@ -432,15 +435,17 @@ module StructuredEncryptionHeader {
   }
 
   // How many elements of Schema are included in the signature?
-  function method CountAuthAttrs(data : CanonCryptoList)
+  function method CountAuthAttrs(data : CanonCryptoList, pos : nat := 0, acc : nat := 0)
     : nat
+    requires 0 <= pos <= |data|
+    decreases |data| - pos
   {
-    if |data| == 0 then
-      0
-    else if IsAuthAttr(data[0].action) then
-      1 + CountAuthAttrs(data[1..])
+    if |data| == pos then
+      acc
+    else if IsAuthAttr(data[pos].action) then
+      CountAuthAttrs(data, pos+1, acc+1)
     else
-      CountAuthAttrs(data[1..])
+      CountAuthAttrs(data, pos+1, acc)
   }
 
   // Legend to Bytes
@@ -470,11 +475,12 @@ module StructuredEncryptionHeader {
               && ret.value.0 == data[2..ret.value.1]
   {
     :- Need(2 <= |data|, E("Unexpected end of header data."));
-    var len := SeqToUInt16(data[0..2]);
+    var len := SeqPosToUInt16(data, 0);
     var size := len as nat + 2;
     :- Need(size <= |data|, E("Unexpected end of header data."));
-    :- Need(forall x <- data[2..size] :: ValidLegendByte(x), E("Invalid byte in stored legend"));
-    Success((data[2..size], size))
+    var legend := data[2..size];
+    :- Need(forall x <- legend :: ValidLegendByte(x), E("Invalid byte in stored legend"));
+    Success((legend, size))
   }
 
   // Bytes to Encryption Context
@@ -484,77 +490,78 @@ module StructuredEncryptionHeader {
               && ret.value.1 <= |data|
     ensures (
               && 2 <= |data|
-              && GetContext2(SeqToUInt16(data[0..2]) as nat, data, data[2..], (map[], 2)).Success?
+              && GetContext2(SeqPosToUInt16(data, 0) as nat, data, (map[], 2)).Success?
             ) ==> ret.Success?
   {
     :- Need(2 <= |data|, E("Unexpected end of header data."));
-    var count := SeqToUInt16(data[0..2]) as nat;
-    var context :- GetContext2(count, data, data[2..], (map[], 2));
+    var count := SeqPosToUInt16(data, 0) as nat;
+    var context :- GetContext2(count, data, (map[], 2));
     Success(context)
   }
 
   // Bytes to one Key Value pair
-  function method GetOneKVPair(data : Bytes)
+  function method GetOneKVPair(data : Bytes, pos : nat)
     : (ret : Result<(CMPUtf8Bytes, CMPUtf8Bytes, nat), Error>)
     ensures ret.Success? ==>
-              && ret.value.2 <= |data|
-              && SerializeOneKVPair(ret.value.0, ret.value.1) == data[..ret.value.2]
+              && ret.value.2 + pos <= |data|
+              && SerializeOneKVPair(ret.value.0, ret.value.1) == data[pos..pos+ret.value.2]
     ensures (
-              && 2 <= |data|
-              && var keyLen := SeqToUInt16(data[0..2]) as nat;
-              && keyLen + 4 <= |data|
-              && UTF8.ValidUTF8Seq(data[2..keyLen+2])
-              && var valueLen := SeqToUInt16(data[keyLen+2..keyLen+4]) as nat;
-              && keyLen + valueLen + 4 <= |data|
-              && UTF8.ValidUTF8Seq(data[keyLen+4..keyLen + valueLen + 4])
-            ) <==> ret.Success? && SerializeOneKVPair(ret.value.0, ret.value.1) == data[..ret.value.2]
-  {
-    :- Need(2 <= |data|, E("Unexpected end of header data."));
-    var keyLen := SeqToUInt16(data[0..2]) as nat;
-    :- Need(keyLen + 4 <= |data|, E("Unexpected end of header data."));
-    var key := data[2..keyLen+2];
+              && 2 + pos <= |data|
+              && var keyLen := SeqPosToUInt16(data, pos) as nat;
+              && keyLen + 4 + pos <= |data|
+              && UTF8.ValidUTF8Seq(data[2+pos..keyLen+2+pos])
+              && var valueLen := SeqPosToUInt16(data, keyLen+2+pos) as nat;
+              && keyLen + valueLen + 4 + pos <= |data|
+              && UTF8.ValidUTF8Seq(data[keyLen+4+pos..keyLen + valueLen + 4 + pos])
+            ) <==> ret.Success? && SerializeOneKVPair(ret.value.0, ret.value.1) == data[pos..pos+ret.value.2]
+  {
+    :- Need(2 + pos <= |data|, E("Unexpected end of header data."));
+    var keyLen := SeqPosToUInt16(data, pos) as nat;
+    :- Need(keyLen + 4 +pos <= |data|, E("Unexpected end of header data."));
+    var key := data[2+pos..keyLen+2+pos];
     :- Need(UTF8.ValidUTF8Seq(key), E("Invalid UTF8 found in header."));
-    var valueLen := SeqToUInt16(data[keyLen+2..keyLen+4]) as nat;
+    var valueLen := SeqPosToUInt16(data, keyLen+2+pos) as nat;
     var kvLen := 2 + keyLen + 2 + valueLen;
-    :- Need(kvLen <= |data|, E("Unexpected end of header data."));
-    var value := data[keyLen+4..kvLen];
+    :- Need(kvLen + pos <= |data|, E("Unexpected end of header data."));
+    var value := data[keyLen+4+pos..kvLen+pos];
     :- Need(UTF8.ValidUTF8Seq(value), E("Invalid UTF8 found in header."));
     Success((key, value, kvLen))
   }
 
-  predicate method {:tailrecursion} BytesLess(a: Bytes, b : Bytes) {
+  predicate method {:tailrecursion} BytesLess(a: Bytes, b : Bytes, pos : nat := 0)
+    requires 0 <= pos <= |a|
+    requires 0 <= pos <= |b|
+    decreases |a| - pos
+  {
     if a == b then
       false
-    else if |a| == 0 then
+    else if |a| == pos then
       true
-    else if |b| == 0 then
+    else if |b| == pos then
       false
-    else if a[0] != b[0] then
-      a[0] < b[0]
+    else if a[pos] != b[pos] then
+      a[pos] < b[pos]
     else
-      BytesLess(a[1..], b[1..])
+      BytesLess(a, b, pos+1)
   }
 
   // For "count" items, Deserialize key value pairs into an Encryption Context
   function method {:tailrecursion} GetContext2(
     count : nat,
-    origData : Bytes,
     data : Bytes,
     deserialized : (CMPEncryptionContext, nat),
     prevKey : CMPUtf8Bytes := [])
     : (ret : Result<(CMPEncryptionContext, nat), Error>)
-    requires deserialized.1 <= |origData|
-    requires deserialized.1 + |data| == |origData|
-    requires data == origData[deserialized.1..]
+    requires deserialized.1 <= |data|
     ensures ret.Success? ==>
-              && ret.value.1 <= |origData|
-              && (count > 0 ==> GetOneKVPair(data).Success?)
+              && ret.value.1 <= |data|
+              && (count > 0 ==> GetOneKVPair(data, deserialized.1).Success?)
   {
     if count == 0 then
       Success(deserialized)
     else
       :- Need(|deserialized.0| + 1  < UINT16_LIMIT, E("Too much context"));
-      var kv :- GetOneKVPair(data);
+      var kv :- GetOneKVPair(data, deserialized.1);
       //= specification/structured-encryption/header.md#key-value-pair-entries
       //# This sequence MUST NOT contain duplicate entries.
       // if the previous key is always less than the current key, there can be no duplicates
@@ -563,7 +570,7 @@ module StructuredEncryptionHeader {
       //# These entries MUST have entries sorted, by key,
       //# in ascending order according to the UTF-8 encoded binary value.
       :- Need(BytesLess(prevKey, kv.0), E("Context keys out of order."));
-      GetContext2(count-1, origData, data[2+|kv.0|+2+|kv.1|..], (deserialized.0[kv.0 := kv.1], deserialized.1 + kv.2), kv.0)
+      GetContext2(count-1, data, (deserialized.0[kv.0 := kv.1], deserialized.1 + kv.2), kv.0)
   }
 
   // Encryption Context to Bytes
@@ -641,30 +648,30 @@ module StructuredEncryptionHeader {
   }
 
   // Bytes to Data Key
-  function method {:vcs_split_on_every_assert} GetOneDataKey(data : Bytes)
+  function method {:vcs_split_on_every_assert} GetOneDataKey(data : Bytes, pos : nat)
     : (ret : Result<(CMPEncryptedDataKey, nat), Error>)
     ensures ret.Success? ==>
-              && ret.value.1 <= |data|
+              && ret.value.1 + pos <= |data|
               && |SerializeOneDataKey(ret.value.0)| == ret.value.1
-              && SerializeOneDataKey(ret.value.0) == data[0..ret.value.1]
+              && SerializeOneDataKey(ret.value.0) == data[pos..pos+ret.value.1]
   {
-    :- Need(2 < |data|, E("Unexpected end of header data."));
-    var provIdSize := SeqToUInt16(data[0..2]) as nat;
-    :- Need(provIdSize + 2 < |data|, E("Unexpected end of header data."));
-    var provId := data[2..2+provIdSize];
+    :- Need(2 + pos < |data|, E("Unexpected end of header data."));
+    var provIdSize := SeqPosToUInt16(data, pos) as nat;
+    :- Need(provIdSize + 2 + pos < |data|, E("Unexpected end of header data."));
+    var provId := data[pos+2..pos+2+provIdSize];
     :- Need(UTF8.ValidUTF8Seq(provId), E("Invalid UTF8 found in header."));
     var part1Size := 2 + provIdSize;
 
-    :- Need(part1Size+2 <= |data|, E("Unexpected end of header data."));
-    var provInfoSize := SeqToUInt16(data[part1Size..part1Size+2]) as nat;
-    :- Need(part1Size + provInfoSize + 2 < |data|, E("Unexpected end of header data."));
-    var provInfo := data[part1Size+2..part1Size+2+provInfoSize];
+    :- Need(part1Size+2 + pos <= |data|, E("Unexpected end of header data."));
+    var provInfoSize := SeqPosToUInt16(data, pos+part1Size) as nat;
+    :- Need(part1Size + provInfoSize + 2 + pos < |data|, E("Unexpected end of header data."));
+    var provInfo := data[pos+part1Size+2..pos+part1Size+2+provInfoSize];
     var part2Size := part1Size + 2 + provInfoSize;
 
-    :- Need(part2Size+2 <= |data|, E("Unexpected end of header data."));
-    var cipherSize := SeqToUInt16(data[part2Size..part2Size+2]) as nat;
-    :- Need(part2Size + cipherSize + 2 <= |data|, E("Unexpected end of header data."));
-    var cipher := data[part2Size+2..part2Size+2+cipherSize];
+    :- Need(part2Size+2+pos <= |data|, E("Unexpected end of header data."));
+    var cipherSize := SeqPosToUInt16(data, pos+part2Size) as nat;
+    :- Need(part2Size + cipherSize + 2 + pos <= |data|, E("Unexpected end of header data."));
+    var cipher := data[pos+part2Size+2..pos+part2Size+2+cipherSize];
     var part3Size := part2Size + 2 + cipherSize;
 
     var edk := CMP.EncryptedDataKey(keyProviderId := provId, keyProviderInfo := provInfo, ciphertext := cipher);
@@ -672,14 +679,21 @@ module StructuredEncryptionHeader {
   }
 
   // for items in "keys", turn Key Value Pairs into Bytes
-  function method {:tailrecursion} SerializeContext2(keys : seq<CMPUtf8Bytes>, x : CMPEncryptionContext)
+  function method {:tailrecursion} SerializeContext2(
+    keys : seq<CMPUtf8Bytes>,
+    x : CMPEncryptionContext,
+    pos : nat := 0,
+    acc : Bytes := []
+  )
     : (ret : Bytes)
     requires forall k <- keys :: k in x
+    requires 0 <= pos <= |keys|
+    decreases |keys| - pos
   {
-    if |keys| == 0 then
-      []
+    if |keys| == pos then
+      acc
     else
-      SerializeOneKVPair(keys[0], x[keys[0]]) + SerializeContext2(keys[1..], x)
+      SerializeContext2(keys, x, pos+1, acc + SerializeOneKVPair(keys[pos], x[keys[pos]]))
   }
 
   // Data Key List to Bytes
@@ -702,13 +716,19 @@ module StructuredEncryptionHeader {
   }
 
   // Data Keys to Bytes
-  function method {:tailrecursion} SerializeDataKeys2(x : CMPEncryptedDataKeyListEmptyOK)
+  function method {:tailrecursion} SerializeDataKeys2(
+    x : CMPEncryptedDataKeyListEmptyOK,
+    pos : nat := 0,
+    acc : Bytes := []
+  )
     : (ret : Bytes)
+    requires 0 <= pos <= |x|
+    decreases |x| - pos
   {
-    if |x| == 0 then
-      []
+    if |x| == pos then
+      acc
     else
-      SerializeOneDataKey(x[0]) + SerializeDataKeys2(x[1..])
+      SerializeDataKeys2(x, pos+1, acc + SerializeOneDataKey(x[pos]))
   }
 
   // Bytes to Data Key List
@@ -719,11 +739,11 @@ module StructuredEncryptionHeader {
               && 1 <= |data|
               && 1 <= ret.value.1
               && |ret.value.0| == data[0] as nat
-              && GetDataKeys2(|ret.value.0|, |ret.value.0|, data, data[1..], ([], 1)).Success?
+              && GetDataKeys2(|ret.value.0|, |ret.value.0|, data, ([], 1)).Success?
   {
     :- Need(1 <= |data|, E("Unexpected end of header data."));
     var count := data[0] as nat;
-    var keys :- GetDataKeys2(count, count, data, data[1..], ([], 1));
+    var keys :- GetDataKeys2(count, count, data, ([], 1));
     if |keys.0| == 0 then
       Failure(E("At least one Data Key required"))
     else
@@ -734,18 +754,15 @@ module StructuredEncryptionHeader {
   function method {:tailrecursion} GetDataKeys2(
     count : nat,
     origCount : nat,
-    origData : Bytes,
     data : Bytes,
-    deserialized
-    : (CMPEncryptedDataKeyListEmptyOK, nat))
+    deserialized : (CMPEncryptedDataKeyListEmptyOK, nat))
     : (ret : Result<(CMPEncryptedDataKeyListEmptyOK, nat), Error>)
-    requires deserialized.1 <= |origData|
-    requires deserialized.1 + |data| == |origData|
+    requires deserialized.1 <= |data|
     requires origCount == count + |deserialized.0|
     ensures ret.Success? ==>
-              && ret.value.1 <= |origData|
+              && ret.value.1 <= |data|
               && ret.value.1 >= deserialized.1
-              && (count > 0 ==> GetOneDataKey(data).Success?)
+              && (count > 0 ==> GetOneDataKey(data, deserialized.1).Success?)
               && |ret.value.0| == origCount
   {
     if count == 0 then
@@ -754,9 +771,9 @@ module StructuredEncryptionHeader {
     if |deserialized.0| >= 255 then
       Failure(E("Too Many Data Keys"))
     else
-      var edk :- GetOneDataKey(data);
-      assert SerializeOneDataKey(edk.0) == data[..edk.1];
-      GetDataKeys2(count-1, origCount, origData, data[edk.1..], (deserialized.0 + [edk.0], deserialized.1+edk.1))
+      var edk :- GetOneDataKey(data, deserialized.1);
+      assert SerializeOneDataKey(edk.0) == data[deserialized.1..deserialized.1+edk.1];
+      GetDataKeys2(count-1, origCount, data, (deserialized.0 + [edk.0], deserialized.1+edk.1))
   }
 
   // End code, begin proofs
@@ -790,7 +807,7 @@ module StructuredEncryptionHeader {
 
   // SerializeOneKVPair ==> GetOneKVPair
   lemma SerializeOneKVPairRoundTrip(key : CMPUtf8Bytes, value : CMPUtf8Bytes)
-    ensures GetOneKVPair(SerializeOneKVPair(key, value)).Success?
+    ensures GetOneKVPair(SerializeOneKVPair(key, value), 0).Success?
   {
     var data := SerializeOneKVPair(key, value);
     assert 2 <= |data|;
@@ -806,9 +823,9 @@ module StructuredEncryptionHeader {
 
   // GetOneKVPair ==> SerializeOneKVPair
   lemma GetOneKVPairRoundTrip(data : Bytes)
-    requires GetOneKVPair(data).Success?
+    requires GetOneKVPair(data, 0).Success?
     ensures
-      && var cont := GetOneKVPair(data).value;
+      && var cont := GetOneKVPair(data, 0).value;
       && SerializeOneKVPair(cont.0, cont.1) == data[..cont.2]
   {}
 
@@ -816,9 +833,9 @@ module StructuredEncryptionHeader {
   lemma SerializeOneDataKeyRoundTrip(k : CMPEncryptedDataKey)
     ensures
       && var data := SerializeOneDataKey(k);
-      && GetOneDataKey(data).Success?
-      && GetOneDataKey(data).value.0 == k
-      && GetOneDataKey(data).value.1 == |data|
+      && GetOneDataKey(data, 0).Success?
+      && GetOneDataKey(data, 0).value.0 == k
+      && GetOneDataKey(data, 0).value.1 == |data|
   {
     var data := SerializeOneDataKey(k);
     assert 2 <= |data|;
@@ -837,9 +854,9 @@ module StructuredEncryptionHeader {
 
   // GetOneDataKey ==> SerializeOneDataKey
   lemma GetOneDataKeyRoundTrip(data : Bytes)
-    requires GetOneDataKey(data).Success?
+    requires GetOneDataKey(data, 0).Success?
     ensures
-      && var cont := GetOneDataKey(data).value;
+      && var cont := GetOneDataKey(data, 0).value;
       && SerializeOneDataKey(cont.0) == data[..cont.1]
   {}
 
@@ -847,10 +864,10 @@ module StructuredEncryptionHeader {
   // the unexamined bytes do not change the result
   lemma GetOneKVPairExt(x : Bytes, y : Bytes)
     requires x <= y
-    requires GetOneKVPair(x).Success?
+    requires GetOneKVPair(x, 0).Success?
     ensures
-      && GetOneKVPair(y).Success?
-      && GetOneKVPair(x).value == GetOneKVPair(y).value
+      && GetOneKVPair(y, 0).Success?
+      && GetOneKVPair(x, 0).value == GetOneKVPair(y, 0).value
   {
     assert 2 <= |y|;
     var keyLen := SeqToUInt16(y[0..2]) as nat;
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Paths.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Paths.dfy
index f5b8bba4e..5b64497bb 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Paths.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Paths.dfy
@@ -110,7 +110,7 @@ module StructuredEncryptionPaths {
   }
 
   // get the Canonical Path for these Selectors
-  function method {:tailrecursion} MakeCanonicalPath(path : Path)
+  function MakeCanonicalPath(path : Path)
     : (ret : CanonicalPath)
     requires ValidPath(path)
     ensures |path| == 0 ==> ret == []
@@ -120,8 +120,35 @@ module StructuredEncryptionPaths {
       []
     else
       CanonicalPart(path[0]) + MakeCanonicalPath(path[1..])
+  } by method {
+    var result : CanonicalPath := [];
+    for i := |path| downto 0
+      invariant result == MakeCanonicalPath(path[i..])
+    {
+      result := CanonicalPart(path[i]) + result;
+    }
+    return result;
   }
 
+  // get the Canonical Path for these Selectors
+  // function method {:tailrecursion} MakeCanonicalPath(path : Path, pos : nat := 0, acc : CanonicalPath := [])
+  //   : (ret : CanonicalPath)
+  //   requires ValidPath(path)
+  //   requires pos <= |path|
+  //   requires pos == 0 ==> |acc| == 0
+  //   requires pos == 1 ==> acc == CanonicalPart(path[0])
+  //   requires acc == MakeCanonicalPathGhost(path[..pos])
+  //   ensures |path| == 0 && pos == 0 ==> ret == []
+  //   ensures |path| == 1 ==> ret == CanonicalPart(path[0])
+  //   ensures ret == MakeCanonicalPathGhost(path)
+  //   decreases |path| - pos
+  // {
+  //   if |path| == pos then
+  //     acc
+  //   else
+  //     MakeCanonicalPath(path, pos+1, acc + CanonicalPart(path[pos]))
+  // }
+
   // Does NOT guarantee a unique output for every unique input
   // e.g. ['a.b'] and ['a','b'] both return 'a.b'.
   function method PathToString(path : Path) : string
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Util.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Util.dfy
index 93e36cd56..20cb74b34 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Util.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Util.dfy
@@ -199,14 +199,16 @@ module StructuredEncryptionUtil {
 
   // sequences are equal if zero is returned
   // Some care should be taken to ensure that target languages don't over optimize this.
-  function method {:tailrecursion} ConstantTimeCompare(a : Bytes, b : Bytes, acc : bv8 := 0) : bv8
+  function method {:tailrecursion} ConstantTimeCompare(a : Bytes, b : Bytes, pos : nat := 0, acc : bv8 := 0) : bv8
     requires |a| == |b|
+    requires 0 <= pos <= |a|
+    decreases |a| - pos
   {
-    if |a| == 0 then
+    if |a| == pos then
       acc
     else
-      var x := ((a[0] as bv8) ^ (b[0] as bv8));
-      ConstantTimeCompare(a[1..], b[1..], x | acc)
+      var x := ((a[pos] as bv8) ^ (b[pos] as bv8));
+      ConstantTimeCompare(a, b, pos+1, x | acc)
   }
 
   predicate method ConstantTimeEquals(a : Bytes, b : Bytes)
diff --git a/TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy b/TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy
index 5ee680b4e..551dd83b7 100644
--- a/TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy
+++ b/TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy
@@ -168,11 +168,13 @@ module {:options "-functionSyntax:4"} DecryptManifest {
       }
     }
 
+    var time := Time.GetAbsoluteTime();
     for i := 0 to |tests.value| {
       var obj := tests.value[i];
       :- Need(obj.1.Object?, "Value of test '" + obj.0 + "' must be an Object.");
       var _ :- OneTest(obj.0, obj.1, keyVectors);
     }
+    Time.PrintTimeSinceLong(time, "DB-ESDK-TV " + inFile);
 
     timeStamp :- expect Time.GetCurrentTimeStamp();
     print timeStamp + " Tests Complete.\n";
diff --git a/submodules/MaterialProviders b/submodules/MaterialProviders
index 786404f1d..70d6a59a7 160000
--- a/submodules/MaterialProviders
+++ b/submodules/MaterialProviders
@@ -1 +1 @@
-Subproject commit 786404f1d1bdf86814c5018ce0b3b1149ef65b3e
+Subproject commit 70d6a59a732261e4aee3ccf6f69dfeb319f3bf3e
diff --git a/submodules/smithy-dafny b/submodules/smithy-dafny
index 186339f25..5fb3f25ea 160000
--- a/submodules/smithy-dafny
+++ b/submodules/smithy-dafny
@@ -1 +1 @@
-Subproject commit 186339f258f9116a3c25cc781c747ab0e94e9dc6
+Subproject commit 5fb3f25ea3444c51b2ad30b25ab03964cf866cd1

From 87b103c02a0841117fac9983fb6cd6355e652349 Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Mon, 10 Feb 2025 09:00:36 -0500
Subject: [PATCH 10/26] m

---
 DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy | 1 +
 1 file changed, 1 insertion(+)

diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy
index 698dc237b..e8b0eb240 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Canonize.dfy
@@ -826,6 +826,7 @@ module {:options "/functionSyntax:4" } Canonize {
     else if xs[pos].key in [HeaderPath, FooterPath] then
       RemoveHeaderPaths(xs, pos+1, acc)
     else
+      assert xs[pos] !in acc;
       RemoveHeaderPaths(xs, pos+1, acc + [xs[pos]])
   }
 

From b60be2b0b458f72980b3aac230869d11b86287f6 Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Mon, 10 Feb 2025 09:11:20 -0500
Subject: [PATCH 11/26] m

---
 DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy | 1 -
 1 file changed, 1 deletion(-)

diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy
index fd016868d..5930ae6ce 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy
@@ -504,7 +504,6 @@ module StructuredEncryptionHeader {
     : (ret : Result<(CMPUtf8Bytes, CMPUtf8Bytes, nat), Error>)
     ensures ret.Success? ==>
               && ret.value.2 + pos <= |data|
-              && SerializeOneKVPair(ret.value.0, ret.value.1) == data[pos..pos+ret.value.2]
     ensures (
               && 2 + pos <= |data|
               && var keyLen := SeqPosToUInt16(data, pos) as nat;

From 8b183a8d5c981a4fcfc4703859196b80747aeee6 Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Mon, 10 Feb 2025 09:29:24 -0500
Subject: [PATCH 12/26] m

---
 project.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/project.properties b/project.properties
index 28a3b90f1..1bba22bde 100644
--- a/project.properties
+++ b/project.properties
@@ -1,6 +1,6 @@
 projectJavaVersion=3.8.0-SNAPSHOT
 mplDependencyJavaVersion=1.9.0-SNAPSHOT
 dafnyVersion=4.9.0
-dafnyVerifyVersion=4.9.0
+dafnyVerifyVersion=4.9.1
 dafnyRuntimeJavaVersion=4.9.0
 smithyDafnyJavaConversionVersion=0.1.1

From 005f287bf947e37f1dbdbc4e3bca1b7c3f15b4cf Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Mon, 10 Feb 2025 11:11:14 -0500
Subject: [PATCH 13/26] m

---
 .../dafny/StructuredEncryption/src/OptimizedMergeSort.dfy       | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
index 5c5152a79..7b3ba1b36 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
@@ -429,7 +429,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
       // Second this would create two loops.
       // First loop would create the `lo to hi` range of numbers.
       // The second loop would then loop over these elements.
-      // A single loop with 
+      // A single loop with
       for i := lo to hi
         modifies right
         invariant left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo])

From a0b4787ef1f745bfa9e217169640752ba88497d1 Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Wed, 12 Feb 2025 16:13:55 -0500
Subject: [PATCH 14/26] m

---
 .../DynamoDbEncryption/src/CompoundBeacon.dfy |  9 ++--
 .../DynamoDbEncryption/src/DynamoToStruct.dfy | 42 +++++++++++++------
 .../DynamoDbEncryption/src/FilterExpr.dfy     | 24 ++++++-----
 .../dafny/DynamoDbEncryption/src/Virtual.dfy  |  3 +-
 .../dafny/StructuredEncryption/src/Header.dfy |  8 ++--
 .../DDBEncryption/src/DecryptManifest.dfy     | 11 ++++-
 submodules/MaterialProviders                  |  2 +-
 7 files changed, 65 insertions(+), 34 deletions(-)

diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/CompoundBeacon.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/CompoundBeacon.dfy
index e8fc00e35..bb1dd0883 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/CompoundBeacon.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/CompoundBeacon.dfy
@@ -20,6 +20,7 @@ module CompoundBeacon {
   import UTF8
   import Seq
   import SortedSets
+  import StandardLibrary.Sequence
 
   type Prefix = x : string | 0 < |x| witness *
 
@@ -289,7 +290,7 @@ module CompoundBeacon {
     // return all the fields involved in this beacon
     function method GetFields(virtualFields : VirtualFieldMap) : seq<string>
     {
-      Seq.Flatten(Seq.Map((p : BeaconPart) => p.GetFields(virtualFields), parts))
+      Sequence.Flatten(Seq.Map((p : BeaconPart) => p.GetFields(virtualFields), parts))
     }
 
     // calculate value for a single piece of a compound beacon query string
@@ -315,9 +316,9 @@ module CompoundBeacon {
         Failure(E("CompoundBeacon " + base.name + " can only be queried as a string, not as " + AttrTypeToStr(value)))
       else
         var parts := Split(value.S, split);
-        var partsUsed :- Seq.MapWithResult(s => getPartFromPrefix(s), parts);
+        var partsUsed :- Sequence.MapWithResult(s => getPartFromPrefix(s), parts);
         var _ :- ValidatePartOrder(partsUsed, value.S);
-        var beaconParts :- Seq.MapWithResult(s => FindAndCalcPart(s, keys), parts);
+        var beaconParts :- Sequence.MapWithResult(s => FindAndCalcPart(s, keys), parts);
         var lastIsPrefix :- justPrefix(Seq.Last(parts));
         if !forEquality && lastIsPrefix then
           var result := Join(beaconParts[..|parts|-1] + [Seq.Last(parts)], [split]);
@@ -534,7 +535,7 @@ module CompoundBeacon {
       requires pos < |parts|
     {
       var partNumbers : seq<nat> := seq(|parts|, (i : nat) => i as nat);
-      var _ :- Seq.MapWithResult((p : int) requires 0 <= p < |parts| => CheckOnePrefixPart(pos, p), seq(|parts|, i => i));
+      var _ :- Sequence.MapWithResult((p : int) requires 0 <= p < |parts| => CheckOnePrefixPart(pos, p), seq(|parts|, i => i));
       Success(true)
     }
 
diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoToStruct.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoToStruct.dfy
index 616dfe404..4306dfb2a 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoToStruct.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/DynamoToStruct.dfy
@@ -18,6 +18,7 @@ module DynamoToStruct {
   import Seq
   import Norm = DynamoDbNormalizeNumber
   import SE = StructuredEncryptionUtil
+  import StandardLibrary.Sequence
 
   type Error = AwsCryptographyDbEncryptionSdkDynamoDbTypes.Error
 
@@ -437,7 +438,7 @@ module DynamoToStruct {
     :- Need(|asSet| == |ns|, "Number Set had duplicate values");
     Seq.LemmaNoDuplicatesCardinalityOfSet(ns);
 
-    var normList :- Seq.MapWithResult(n => Norm.NormalizeNumber(n), ns);
+    var normList :- Sequence.MapWithResult(n => Norm.NormalizeNumber(n), ns);
     var asSet := Seq.ToSet(normList);
     :- Need(|asSet| == |normList|, "Number Set had duplicate values after normalization.");
 
@@ -454,6 +455,10 @@ module DynamoToStruct {
 
   function method BinarySetAttrToBytes(bs: BinarySetAttributeValue): (ret: Result<seq<uint8>, string>)
     ensures ret.Success? ==> Seq.HasNoDuplicates(bs)
+    ensures ret.Success? ==>
+              && U32ToBigEndian(|bs|).Success?
+              && LENGTH_LEN <= |ret.value|
+              && ret.value[..LENGTH_LEN] == U32ToBigEndian(|bs|).value
   {
     var asSet := Seq.ToSet(bs);
     :- Need(|asSet| == |bs|, "Binary Set had duplicate values");
@@ -561,14 +566,17 @@ module DynamoToStruct {
   // String Set or Number Set to Bytes
   function method {:tailrecursion} {:opaque} CollectString(
     setToSerialize : StringSetAttributeValue,
+    pos : nat := 0,
     serialized : seq<uint8> := [])
     : Result<seq<uint8>, string>
+    requires pos <= |setToSerialize|
+    decreases |setToSerialize| - pos
   {
-    if |setToSerialize| == 0 then
+    if |setToSerialize| == pos then
       Success(serialized)
     else
-      var entry :- EncodeString(setToSerialize[0]);
-      CollectString(setToSerialize[1..], serialized + entry)
+      var entry :- EncodeString(setToSerialize[pos]);
+      CollectString(setToSerialize, pos+1, serialized + entry)
   }
 
 
@@ -596,13 +604,19 @@ module DynamoToStruct {
   }
 
   // Binary Set to Bytes
-  function method {:tailrecursion} CollectBinary(setToSerialize : BinarySetAttributeValue, serialized : seq<uint8> := []) : Result<seq<uint8>, string>
+  function method {:tailrecursion} CollectBinary(
+    setToSerialize : BinarySetAttributeValue,
+    pos : nat := 0,
+    serialized : seq<uint8> := []
+  ) : Result<seq<uint8>, string>
+    requires pos <= |setToSerialize|
+    decreases |setToSerialize| - pos
   {
-    if |setToSerialize| == 0 then
+    if |setToSerialize| == pos then
       Success(serialized)
     else
-      var item :- SerializeBinaryValue(setToSerialize[0]);
-      CollectBinary(setToSerialize[1..], serialized + item)
+      var item :- SerializeBinaryValue(setToSerialize[pos]);
+      CollectBinary(setToSerialize, pos+1, serialized + item)
   }
 
   // List to Bytes
@@ -641,6 +655,7 @@ module DynamoToStruct {
     if |listToSerialize| == 0 then
       Success(serialized)
     else
+      // Can't do the `pos` optimization, because I can't appease the `decreases`
       var val :- AttrToBytes(listToSerialize[0], true, depth+1);
       CollectList(listToSerialize[1..], depth, serialized + val)
   }
@@ -705,24 +720,27 @@ module DynamoToStruct {
     //# Entries in a serialized Map MUST be ordered by key value,
     //# ordered in ascending [UTF-16 binary order](./string-ordering.md#utf-16-binary-order).
     var keys := SortedSets.ComputeSetToOrderedSequence2(mapToSerialize.Keys, CharLess);
-    CollectOrderedMapSubset(keys, mapToSerialize, serialized)
+    CollectOrderedMapSubset(keys, mapToSerialize, 0, serialized)
   }
 
   function method {:tailrecursion} {:opaque} CollectOrderedMapSubset(
     keys : seq<AttributeName>,
     mapToSerialize : map<AttributeName, seq<uint8>>,
+    pos : nat := 0,
     serialized : seq<uint8> := []
   )
     : (ret : Result<seq<uint8>, string>)
+    requires pos <= |keys|
     requires forall k <- keys :: k in mapToSerialize
     ensures (ret.Success? && |keys| == 0) ==> (ret.value == serialized)
     ensures (ret.Success? && |keys| == 0) ==> (|ret.value| == |serialized|)
+    decreases |keys| - pos
   {
-    if |keys| == 0 then
+    if |keys| == pos then
       Success(serialized)
     else
-      var data :- SerializeMapItem(keys[0], mapToSerialize[keys[0]]);
-      CollectOrderedMapSubset(keys[1..], mapToSerialize, serialized + data)
+      var data :- SerializeMapItem(keys[pos], mapToSerialize[keys[pos]]);
+      CollectOrderedMapSubset(keys, mapToSerialize, pos+1, serialized + data)
   }
 
   function method BoolToUint8(b : bool) : uint8
diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/FilterExpr.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/FilterExpr.dfy
index 2f2a2cff9..5a0d25ca0 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/FilterExpr.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/FilterExpr.dfy
@@ -1271,25 +1271,27 @@ module DynamoDBFilterExpr {
   // If only surrogates are involved, comparison is normal
   // if one surrogate is involved, the surrogate is larger
   // results undefined if not valid UTF16 encodings, but the idea of 'less' is also undefined for invalid encodings.
-  predicate method {:tailrecursion} UnicodeLess(a : string, b : string)
+  predicate method {:tailrecursion} UnicodeLess(a : string, b : string, pos : nat := 0)
+    requires pos <= |a|
+    requires pos <= |b|
+    decreases |a| - pos
   {
-    if |a| == 0 && |b| == 0 then
+    if |a| == pos && |b| == pos then
       false
-    else if |a| == 0 then
+    else if |a| == pos then
       true
-    else if |b| == 0 then
+    else if |b| == pos then
       false
+    else if a[pos] == b[pos] then
+      UnicodeLess(a, b, pos+1) // correct independent of surrogate status
     else
-    if a[0] == b[0] then
-      UnicodeLess(a[1..], b[1..]) // correct independent of surrogate status
-    else
-      var aIsHighSurrogate := IsHighSurrogate(a[0]);
-      var bIsHighSurrogate := IsHighSurrogate(b[0]);
+      var aIsHighSurrogate := IsHighSurrogate(a[pos]);
+      var bIsHighSurrogate := IsHighSurrogate(b[pos]);
       if aIsHighSurrogate == bIsHighSurrogate then
-        a[0] < b[0]
+        a[pos] < b[pos]
       else
         bIsHighSurrogate
-        // we know aIsHighSurrogate != bIsHighSurrogate and a[0] != b[0]
+        // we know aIsHighSurrogate != bIsHighSurrogate and a[pos] != b[pos]
         // so if bIsHighSurrogate then a is less
         // and if aIsHighSurrogate then a is greater
   }
diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/Virtual.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/Virtual.dfy
index c3cdbf5f6..1dae70d08 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryption/src/Virtual.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/src/Virtual.dfy
@@ -23,11 +23,12 @@ module DdbVirtualFields {
   import DDB = ComAmazonawsDynamodbTypes
   import Seq
   import TermLoc
+  import StandardLibrary.Sequence
 
 
   function method ParseVirtualFieldConfig(vf : VirtualField) : Result<VirtField, Error>
   {
-    var parts :- Seq.MapWithResult((p : VirtualPart) => ParseVirtualPartConfig(p), vf.parts);
+    var parts :- Sequence.MapWithResult((p : VirtualPart) => ParseVirtualPartConfig(p), vf.parts);
     Success(VirtField(vf.name, parts))
   }
 
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy
index 5930ae6ce..abd39ddd3 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/Header.dfy
@@ -821,11 +821,11 @@ module StructuredEncryptionHeader {
   }
 
   // GetOneKVPair ==> SerializeOneKVPair
-  lemma GetOneKVPairRoundTrip(data : Bytes)
-    requires GetOneKVPair(data, 0).Success?
+  lemma GetOneKVPairRoundTrip(data : Bytes, pos : nat)
+    requires GetOneKVPair(data, pos).Success?
     ensures
-      && var cont := GetOneKVPair(data, 0).value;
-      && SerializeOneKVPair(cont.0, cont.1) == data[..cont.2]
+      && var cont := GetOneKVPair(data, pos).value;
+      && SerializeOneKVPair(cont.0, cont.1) == data[pos..cont.2+pos]
   {}
 
   // SerializeOneDataKey ==> GetOneDataKey
diff --git a/TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy b/TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy
index 551dd83b7..61a325668 100644
--- a/TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy
+++ b/TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy
@@ -21,6 +21,7 @@ module {:options "-functionSyntax:4"} DecryptManifest {
   import JsonConfig
   import ENC = AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes
   import KeyVectors
+  import OsLang
 
   method OnePositiveTest(name : string, config : JSON, encrypted : JSON, plaintext : JSON, keys : KeyVectors.KeyVectorsClient)
     returns (output : Result<bool, string>)
@@ -110,6 +111,14 @@ module {:options "-functionSyntax:4"} DecryptManifest {
     }
   }
 
+  function LogFileName() : string
+  {
+    if OsLang.GetOsShort() == "Windows" && OsLang.GetLanguageShort() == "Dotnet" then
+      "..\\..\\PerfLog.txt"
+    else
+      "../../PerfLog.txt"
+  }
+
   method Decrypt(inFile : string, keyVectors: KeyVectors.KeyVectorsClient)
     returns (output : Result<bool, string>)
     requires keyVectors.ValidState()
@@ -174,7 +183,7 @@ module {:options "-functionSyntax:4"} DecryptManifest {
       :- Need(obj.1.Object?, "Value of test '" + obj.0 + "' must be an Object.");
       var _ :- OneTest(obj.0, obj.1, keyVectors);
     }
-    Time.PrintTimeSinceLong(time, "DB-ESDK-TV " + inFile);
+    Time.PrintTimeSinceLong(time, "DB-ESDK-TV-Decrypt-" + inFile, Some(LogFileName()));
 
     timeStamp :- expect Time.GetCurrentTimeStamp();
     print timeStamp + " Tests Complete.\n";
diff --git a/submodules/MaterialProviders b/submodules/MaterialProviders
index 70d6a59a7..d5c4a20d9 160000
--- a/submodules/MaterialProviders
+++ b/submodules/MaterialProviders
@@ -1 +1 @@
-Subproject commit 70d6a59a732261e4aee3ccf6f69dfeb319f3bf3e
+Subproject commit d5c4a20d97d5e074f874d672f22cef996b2c5079

From bfa2d684c515195efc9d747f59c899982df6b154 Mon Sep 17 00:00:00 2001
From: seebees <ryanemer@amazon.com>
Date: Tue, 3 Dec 2024 13:35:17 -0800
Subject: [PATCH 15/26] Update some of the proof

---
 .../src/OptimizedMergeSort.dfy                | 44 ++++++++++++++-----
 1 file changed, 33 insertions(+), 11 deletions(-)

diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
index 7b3ba1b36..e071bfa9e 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
@@ -105,7 +105,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     requires Relations.TotalOrdering(lessThanOrEq)
     requires lo < hi <= left.Length
     requires hi <= right.Length && left != right
-    // reads left, right
+    reads left, right
     modifies left, right
     ensures !where.Either? ==> where == resultPlacement
 
@@ -217,6 +217,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     requires Relations.SortedBy(left[mid..hi], lessThanOrEq)
     // reads left, right
     modifies right
+    reads left, right
     // We do not modify anything before lo
     ensures right[..lo] == old(right[..lo]) && left[..lo] == old(left[..lo])
     // We do not modify anything above hi
@@ -239,8 +240,10 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
       invariant Below(right[lo..iter], left[leftPosition..mid], lessThanOrEq)
       invariant Below(right[lo..iter], left[rightPosition..hi], lessThanOrEq)
       invariant Relations.SortedBy(right[lo..iter], lessThanOrEq)
-      invariant multiset(right[lo..iter]) == multiset(left[lo..leftPosition]) + multiset(left[mid..rightPosition])
+      invariant multiset(right[lo..iter]) == multiset(left[lo..leftPosition] + left[mid..rightPosition])
     {
+
+      ghost var oldRightPosition, oldIter, oldLeftPosition := rightPosition, iter, leftPosition;
       if leftPosition == mid || (rightPosition < hi && lessThanOrEq(left[rightPosition], left[leftPosition])) {
         right[iter] := left[rightPosition];
 
@@ -255,7 +258,26 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
         PushStillSortedBy(right, lo, iter, lessThanOrEq);
         leftPosition, iter := leftPosition + 1, iter + 1;
 
+        assert 0 < |right[lo..iter]| && 0 < |left[leftPosition..mid]| ==> lessThanOrEq(right[lo..iter][|right[lo..iter]| - 1], left[leftPosition..mid][0]) by {
+          if 0 == |right[lo..iter]| || 0 == |left[leftPosition..mid]| {
+          } else {
+            assert rightPosition == oldRightPosition;
+            assert oldLeftPosition < mid;
+            // This is true, but uncommenting it causes the proof to fail
+            // leaving it here it make what is going on a little more clear
+            // assert right[lo..iter][|right[lo..iter]| - 1] == right[oldIter];
+            assert left[leftPosition..mid][0] == left[leftPosition];
+          }
+        }
         BelowIsTransitive(right[lo..iter], left[leftPosition..mid], lessThanOrEq);
+
+        assert 0 < |right[lo..iter]| && 0 < |left[rightPosition..hi]| ==> lessThanOrEq(right[lo..iter][|right[lo..iter]| - 1], left[rightPosition..hi][0]) by {
+          if 0 == |right[lo..iter]| || 0 == |left[rightPosition..hi]| {
+          } else {
+            assert right[lo..iter][|right[lo..iter]| - 1] == right[iter - 1];
+            assert left[rightPosition..hi][0] == left[rightPosition];
+          }
+        }
         BelowIsTransitive(right[lo..iter], left[rightPosition..hi], lessThanOrEq);
       }
     }
@@ -362,7 +384,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
       && right.Length < UINT64_LIMIT
     requires lo < hi <= left.Length as uint64
     requires hi <= right.Length as uint64 && left != right
-    // reads left, right
+    reads left, right
     modifies left, right
     ensures !where.Either? ==> where == resultPlacement
 
@@ -485,7 +507,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     requires Relations.SortedBy(left[lo..mid], lessThanOrEq)
     // We store "right" in [mid..hi]
     requires Relations.SortedBy(left[mid..hi], lessThanOrEq)
-    // reads left, right
+    reads left, right
     modifies right
     // We do not modify anything before lo
     ensures right[..lo] == old(right[..lo]) && left[..lo] == old(left[..lo])
@@ -509,9 +531,10 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
       invariant Below(right[lo..iter], left[leftPosition..mid], lessThanOrEq)
       invariant Below(right[lo..iter], left[rightPosition..hi], lessThanOrEq)
       invariant Relations.SortedBy(right[lo..iter], lessThanOrEq)
-      invariant multiset(right[lo..iter]) == multiset(left[lo..leftPosition]) + multiset(left[mid..rightPosition])
+      invariant multiset(right[lo..iter]) == multiset(left[lo..leftPosition] + left[mid..rightPosition])
     {
-      label BEFORE_WORK:
+
+      ghost var oldRightPosition, oldIter, oldLeftPosition := rightPosition, iter, leftPosition;
       if leftPosition == mid || (rightPosition < hi && lessThanOrEq(left[rightPosition], left[leftPosition])) {
         right[iter] := left[rightPosition];
 
@@ -529,11 +552,10 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
         leftPosition, iter := leftPosition + 1, iter + 1;
 
         assert 0 < |right[lo..iter]| && 0 < |left[leftPosition..mid]| ==> lessThanOrEq(right[lo..iter][|right[lo..iter]| - 1], left[leftPosition..mid][0]) by {
-          if 0 < |right[lo..iter]| && 0 < |left[leftPosition..mid]| {
-            assert lessThanOrEq(left[leftPosition-1], left[leftPosition]) by {
-              assert lo <= leftPosition-1 < leftPosition < mid;
-              assert Relations.SortedBy(left[lo..mid], lessThanOrEq);
-            }
+          if 0 == |right[lo..iter]| || 0 == |left[leftPosition..mid]| {
+          } else {
+            assert right[lo..iter][|right[lo..iter]| - 1] == right[iter - 1];
+            assert left[leftPosition..mid][0] == left[leftPosition];
           }
         }
         BelowIsTransitive(right[lo..iter], left[leftPosition..mid], lessThanOrEq);

From 40153a582b3cd900844ef28e87519cd556a3d7c4 Mon Sep 17 00:00:00 2001
From: seebees <ryanemer@amazon.com>
Date: Wed, 12 Feb 2025 15:15:27 -0800
Subject: [PATCH 16/26] Updates to keep inline with Dafny standard library.

---
 .../src/OptimizedMergeSort.dfy                | 320 +++++++++++++-----
 .../StructuredEncryption/src/SortCanon.dfy    |   4 +-
 2 files changed, 241 insertions(+), 83 deletions(-)

diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
index e071bfa9e..f85b5cb82 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
@@ -4,11 +4,16 @@
 include "../Model/AwsCryptographyDbEncryptionSdkStructuredEncryptionTypes.dfy"
 
 module {:options "-functionSyntax:4"} OptimizedMergeSort {
-  import Seq.MergeSort
-  import Relations = MergeSort.Relations
-  import opened StandardLibrary.UInt
 
-  // The Seq.MergeSort function implemented as implemented
+  import Relations
+  import BoundedInts
+  import InternalModule = Seq.MergeSort
+
+  predicate HasUint64Len<T>(s: seq<T>) {
+    |s| < BoundedInts.TWO_TO_THE_64
+  }
+
+  // The MergeSortBy function implemented as implemented
   // does not compile to an optimal implementation
   // in any of the Dafny target languages.
   // This implementation aims to be significantly more optimal.
@@ -16,15 +21,80 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
   // It does this by making 2 arrays of the original sequence
   // and then using these 2 as left and right alternatively.
   // This can be audited by verifying
-  // that the arrays are only sliced into a seq in `FastMergeSort`.
+  // that the arrays are only sliced into a seq in `MergeSortNat`.
   // All other slicing is done in ghost state.
   // Second, is has a bounded number implementation
   // that avoids using `nat`.
 
-  function {:isolate_assertions} FastMergeSort<T(!new)>(s: seq<T>, lessThanOrEq: (T, T) -> bool): (result :seq<T>)
+  function {:isolate_assertions} MergeSort<T(!new)>(s: seq<T>, lessThanOrEq: (T, T) -> bool): (result :seq<T>)
+    requires Relations.TotalOrdering(lessThanOrEq)
+    requires HasUint64Len(s)
+  {
+    InternalModule.MergeSortBy(s, lessThanOrEq)
+  }
+  by method {
+    if |s| <= 1 {
+      return s;
+    } else {
+
+      // The slice x[1..], y[1..] are un-optimized operations in Dafny.
+      // This means that their usage will result in a lot of data copying.
+      // Additional, it is very likely that these size of these sequences
+      // will be less than uint64.
+      // So writing an optimized version that only works on bounded types
+      // should further optimized this hot code.
+
+      var left := new T[|s|](i requires 0 <= i < |s| => s[i]);
+      var right := new T[|s|](i requires 0 <= i < |s| => s[i]);
+      var lo, hi := 0, right.Length;
+
+      label BEFORE_WORK:
+
+      var boundedLo: BoundedInts.uint64, boundedHi: BoundedInts.uint64 := 0, right.Length as BoundedInts.uint64;
+      ghost var _ := MergeSortMethod(left, right, lessThanOrEq, boundedLo, boundedHi, Right);
+
+      result :=  right[..];
+
+      ghost var other := InternalModule.MergeSortBy(s, lessThanOrEq);
+
+      assert Relations.SortedBy(right[..], lessThanOrEq) by {
+        assert right[..] == right[lo..hi];
+      }
+      assert multiset(right[..]) == multiset(other) by {
+        calc {
+          multiset(right[..]);
+        == {assert right[..] == right[lo..hi];}
+          multiset(right[lo..hi]);
+        ==
+          multiset(old@BEFORE_WORK(left[lo..hi]));
+        == {assert old@BEFORE_WORK(left[lo..hi]) == s;}
+          multiset(s);
+        ==
+          multiset(other);
+        }
+      }
+
+      // Implementing a by method can be complicated.
+      // Because methods can have non-determinism,
+      // where functions can not.
+      // This means that Dafny normally wants to know
+      // that the method and function maintain equality at every step.
+      // But this is hard for this kind of optimized sorting.
+      // Because what is the functional state at every step
+      // and how does it correspond the state of 2 optimized arrays?
+      // This lemma works around this
+      // by proving that the outcomes are always deterministic and the same.
+      // It does this by proving that given a total ordering,
+      // there is one and only one way to sort a given sequence.
+      TotalOrderingImpliesSortingIsUnique(right[..], other, lessThanOrEq);
+    }
+  }
+
+  // This is included as sugar in case you don't want to ensure your seq HasUint64Len.
+  function {:isolate_assertions} MergeSortNat<T(!new)>(s: seq<T>, lessThanOrEq: (T, T) -> bool): (result :seq<T>)
     requires Relations.TotalOrdering(lessThanOrEq)
   {
-    MergeSort.MergeSortBy(s, lessThanOrEq)
+    InternalModule.MergeSortBy(s, lessThanOrEq)
   }
   by method {
     if |s| <= 1 {
@@ -45,17 +115,22 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
       label BEFORE_WORK:
 
       if HasUint64Len(s) {
-        var boundedLo: uint64, boundedHi: uint64 := 0, right.Length as uint64;
-        ghost var _ := BoundedMergeSortMethod(left, right, lessThanOrEq, boundedLo, boundedHi, Right);
+        var boundedLo: BoundedInts.uint64, boundedHi: BoundedInts.uint64 := 0, right.Length as BoundedInts.uint64;
+        ghost var _ := MergeSortMethod(left, right, lessThanOrEq, boundedLo, boundedHi, Right);
 
         result :=  right[..];
       } else {
-        ghost var _ := MergeSortMethod(left, right, lessThanOrEq, lo, hi, Right);
+        // Fallback to `nat` or BigInt.
+        // This is a little silly, but this ensures
+        // that the behavior for very large seq will be the same.
+        // Though it is likely if any such seq existed in the real world,
+        // the performance improvement here would still not be enough to complete the sort...
+        ghost var _ := NatMergeSortMethod(left, right, lessThanOrEq, lo, hi, Right);
 
         result :=  right[..];
       }
 
-      ghost var other := MergeSort.MergeSortBy(s, lessThanOrEq);
+      ghost var other := InternalModule.MergeSortBy(s, lessThanOrEq);
 
       assert Relations.SortedBy(right[..], lessThanOrEq) by {
         assert right[..] == right[lo..hi];
@@ -93,18 +168,26 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
   datatype PlaceResults = Left | Right | Either
   type ResultPlacement = r: PlaceResults | !r.Either? witness *
 
+  // These are bounded implementations of merge sort.
+  // This further speeds things up
+  // because math with bounded variables
+  // is significantly faster that math with big numbers.
+
   method {:isolate_assertions} MergeSortMethod<T(!new)>(
     left: array<T>,
     right: array<T>,
     lessThanOrEq: (T, T) -> bool,
-    lo: nat,
-    hi: nat,
+    lo: BoundedInts.uint64,
+    hi: BoundedInts.uint64,
     where: PlaceResults
   )
     returns (resultPlacement: ResultPlacement)
     requires Relations.TotalOrdering(lessThanOrEq)
-    requires lo < hi <= left.Length
-    requires hi <= right.Length && left != right
+    requires left.Length < BoundedInts.TWO_TO_THE_64
+    requires right.Length < BoundedInts.TWO_TO_THE_64
+    requires lo < hi <= left.Length as BoundedInts.uint64
+    requires hi <= right.Length as BoundedInts.uint64
+    requires left != right
     reads left, right
     modifies left, right
     ensures !where.Either? ==> where == resultPlacement
@@ -131,7 +214,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     }
 
     ghost var beforeWork := multiset(left[lo..hi]);
-    var mid := (lo + hi) / 2;
+    var mid :=  ((hi - lo)/2) + lo;
     var placement? := MergeSortMethod(left, right, lessThanOrEq, lo, mid, Either);
     assert left[mid..hi] == old(left[mid..hi]);
     ghost var placement2? := MergeSortMethod(left, right, lessThanOrEq, mid, hi, placement?);
@@ -140,33 +223,47 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     ghost var preMergeResult := if placement?.Left? then left else right;
     calc {
       multiset(preMergeResult[lo..hi]);
-    == { assert preMergeResult[lo..hi] == preMergeResult[lo..mid] + preMergeResult[mid..hi]; }
+    == { LemmaSplitAt(preMergeResult[..], lo as nat, mid as nat, hi as nat); }
       multiset(preMergeResult[lo..mid] + preMergeResult[mid..hi]);
     ==
       multiset(old(left[lo..mid]) + old(left[mid..hi]));
-    ==  { assert old(left[lo..hi]) == old(left[lo..mid]) + old(left[mid..hi]); }
+    ==  { LemmaSplitAt(old(left[..]), lo as nat, mid as nat, hi as nat); }
       beforeWork;
     }
 
     ghost var mergedResult;
     if placement?.Left? {
       MergeIntoRight(left := left, right := right, lessThanOrEq := lessThanOrEq, lo := lo, mid := mid, hi := hi);
-      resultPlacement := Right;
+      resultPlacement, mergedResult := Right, right[lo..hi];
 
-      mergedResult := right[lo..hi];
-      assert left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..]);
+      assert left[hi..] == old(left[hi..]);
+      assert right[hi..] == old(right[hi..]);
+      assert left[..lo] == old(left[..lo]);
+      assert right[..lo] == old(right[..lo]);
     } else {
       assert placement?.Right?;
       MergeIntoRight(left := right, right := left, lessThanOrEq := lessThanOrEq, lo := lo, mid := mid, hi := hi);
-      resultPlacement := Left;
+      resultPlacement, mergedResult := Left, left[lo..hi];
 
-      mergedResult := left[lo..hi];
-      assert left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..]);
+      assert left[hi..] == old(left[hi..]);
+      assert right[hi..] == old(right[hi..]);
+      assert left[..lo] == old(left[..lo]);
+      assert right[..lo] == old(right[..lo]);
     }
 
     label BEFORE_RETURN:
-    assert left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo]);
+    assert left[hi..] == old(left[hi..]);
+    assert right[hi..] == old(right[hi..]);
+    assert left[..lo] == old(left[..lo]);
+    assert right[..lo] == old(right[..lo]);
     if resultPlacement.Left? && where == Right {
+      // A forall comprehension might seem like a nice fit here,
+      // however this does not good for two reasons.
+      // First, Dafny currently creates a range for the full bounds of the bounded number
+      // see: https://github.com/dafny-lang/dafny/issues/5897
+      // Second this would create two loops.
+      // First loop would create the `lo to hi` range of numbers.
+      // The second loop would then loop over these elements.
       for i := lo to hi
         modifies right
         invariant left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo])
@@ -174,9 +271,12 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
         invariant right[lo..i] == left[lo..i]
       {
         right[i] := left[i];
+        assert right[lo..i] == left[lo..i];
       }
 
-      assert right[lo..hi] == mergedResult;
+      assert right[lo..hi] == mergedResult by {
+        assert mergedResult == left[lo..hi];
+      }
       assert left[..] == old@BEFORE_RETURN(left[..]);
       assert right[..lo] == old(right[..lo]);
 
@@ -190,9 +290,12 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
         invariant left[lo..i] == right[lo..i]
       {
         left[i] := right[i];
+        assert right[lo..i] == left[lo..i];
       }
 
-      assert left[lo..hi] == mergedResult;
+      assert left[lo..hi] == mergedResult by {
+        assert mergedResult == right[lo..hi];
+      }
       assert right[..] == old@BEFORE_RETURN(right[..]);
       assert left[..lo] == old(left[..lo]);
 
@@ -204,20 +307,22 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     nameonly left: array<T>,
     nameonly right: array<T>,
     nameonly lessThanOrEq: (T, T) -> bool,
-    nameonly lo: nat,
-    nameonly mid: nat,
-    nameonly hi: nat
+    nameonly lo: BoundedInts.uint64,
+    nameonly mid: BoundedInts.uint64,
+    nameonly hi: BoundedInts.uint64
   )
     requires Relations.TotalOrdering(lessThanOrEq)
-    requires lo <= mid <= hi <= left.Length
-    requires  hi <= right.Length && left != right
+    requires
+      && left.Length < BoundedInts.TWO_TO_THE_64
+      && right.Length < BoundedInts.TWO_TO_THE_64
+    requires lo <= mid <= hi <= left.Length as BoundedInts.uint64
+    requires  hi <= right.Length as BoundedInts.uint64 && left != right
     // We store "left" in [lo..mid]
     requires Relations.SortedBy(left[lo..mid], lessThanOrEq)
     // We store "right" in [mid..hi]
     requires Relations.SortedBy(left[mid..hi], lessThanOrEq)
-    // reads left, right
-    modifies right
     reads left, right
+    modifies right
     // We do not modify anything before lo
     ensures right[..lo] == old(right[..lo]) && left[..lo] == old(left[..lo])
     // We do not modify anything above hi
@@ -231,7 +336,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
       modifies right
 
       invariant lo <= leftPosition <= mid <= rightPosition <= hi
-      invariant leftPosition - lo + rightPosition - mid == iter - lo
+      invariant leftPosition as nat - lo as nat + rightPosition as nat - mid as nat == iter as nat - lo as nat
       invariant right[..lo] == old(right[..lo])
       invariant right[hi..] == old(right[hi..])
 
@@ -247,30 +352,37 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
       if leftPosition == mid || (rightPosition < hi && lessThanOrEq(left[rightPosition], left[leftPosition])) {
         right[iter] := left[rightPosition];
 
-        PushStillSortedBy(right, lo, iter, lessThanOrEq);
+        PushStillSortedBy(right, lo as nat, iter as nat, lessThanOrEq);
         rightPosition, iter := rightPosition + 1, iter + 1;
 
         BelowIsTransitive(right[lo..iter], left[leftPosition..mid], lessThanOrEq);
+
+        assert 0 < |right[lo..iter]| && 0 < |left[rightPosition..hi]| ==> lessThanOrEq(right[lo..iter][|right[lo..iter]| - 1], left[rightPosition..hi][0]) by {
+          if 0 == |right[lo..iter]| || 0 == |left[rightPosition..hi]| {
+          } else {
+            assert Relations.SortedBy(left[oldRightPosition..hi], lessThanOrEq);
+            assert lessThanOrEq(left[oldRightPosition..hi][0], left[oldRightPosition..hi][1]);
+          }
+        }
         BelowIsTransitive(right[lo..iter], left[rightPosition..hi], lessThanOrEq);
+
+        assert multiset(right[lo..iter]) == multiset(left[lo..leftPosition] + left[mid..rightPosition]) by {
+          // Dafny just wants to be reminded
+        }
       } else {
         right[iter] := left[leftPosition];
 
-        PushStillSortedBy(right, lo, iter, lessThanOrEq);
+        PushStillSortedBy(right, lo as nat, iter as nat, lessThanOrEq);
         leftPosition, iter := leftPosition + 1, iter + 1;
 
         assert 0 < |right[lo..iter]| && 0 < |left[leftPosition..mid]| ==> lessThanOrEq(right[lo..iter][|right[lo..iter]| - 1], left[leftPosition..mid][0]) by {
           if 0 == |right[lo..iter]| || 0 == |left[leftPosition..mid]| {
           } else {
-            assert rightPosition == oldRightPosition;
-            assert oldLeftPosition < mid;
-            // This is true, but uncommenting it causes the proof to fail
-            // leaving it here it make what is going on a little more clear
-            // assert right[lo..iter][|right[lo..iter]| - 1] == right[oldIter];
-            assert left[leftPosition..mid][0] == left[leftPosition];
+            assert Relations.SortedBy(left[oldLeftPosition..mid], lessThanOrEq);
+            assert lessThanOrEq(left[oldLeftPosition..mid][0], left[oldLeftPosition..mid][1]);
           }
         }
         BelowIsTransitive(right[lo..iter], left[leftPosition..mid], lessThanOrEq);
-
         assert 0 < |right[lo..iter]| && 0 < |left[rightPosition..hi]| ==> lessThanOrEq(right[lo..iter][|right[lo..iter]| - 1], left[rightPosition..hi][0]) by {
           if 0 == |right[lo..iter]| || 0 == |left[rightPosition..hi]| {
           } else {
@@ -279,10 +391,15 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
           }
         }
         BelowIsTransitive(right[lo..iter], left[rightPosition..hi], lessThanOrEq);
+
+        assert multiset(right[lo..iter]) == multiset(left[lo..leftPosition] + left[mid..rightPosition]) by {
+          // Dafny just wants to be reminded
+        }
       }
     }
     assert multiset(right[lo..hi]) == multiset(old(left[lo..hi])) by {
       assert leftPosition == mid && rightPosition == hi;
+      LemmaSplitAt(left[..], lo as nat, mid as nat, hi as nat);
       assert old(left[lo..hi]) == left[lo..hi] == left[lo..mid] + left[mid..hi];
     }
   }
@@ -362,28 +479,37 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     assert a == a' == b' == b;
   }
 
-  // These are bounded implementations of the above.
-  // They do exactly the same thing,
-  // but they use `uint64`.
-  // This further speeds things up
-  // because math with bounded variables
-  // is significantly faster that math with big numbers.
+  lemma LemmaNewFirstElementStillSortedBy<T(!new)>(x: T, s: seq<T>, lessThan: (T, T) -> bool)
+    requires Relations.SortedBy(s, lessThan)
+    requires |s| == 0 || lessThan(x, s[0])
+    requires Relations.TotalOrdering(lessThan)
+    ensures Relations.SortedBy([x] + s, lessThan)
+  {}
 
-  method {:isolate_assertions} BoundedMergeSortMethod<T(!new)>(
+  lemma LemmaSplitAt<T>(s: seq<T>, lo: nat, split: nat, hi: nat)
+    requires 0 <= lo
+    requires lo <= split
+    requires split <= hi
+    requires hi <= |s|
+    ensures s[lo..hi] == s[lo..split] + s[split..hi]
+  {}
+
+  // This is the nat version of merge sort.
+  // This is an exact copy of the bounded integer implementation above
+  // but with `nat` instead of BoundedInts.uint64.
+
+  method {:isolate_assertions} NatMergeSortMethod<T(!new)>(
     left: array<T>,
     right: array<T>,
     lessThanOrEq: (T, T) -> bool,
-    lo: uint64,
-    hi: uint64,
+    lo: nat,
+    hi: nat,
     where: PlaceResults
   )
     returns (resultPlacement: ResultPlacement)
     requires Relations.TotalOrdering(lessThanOrEq)
-    requires
-      && left.Length < UINT64_LIMIT
-      && right.Length < UINT64_LIMIT
-    requires lo < hi <= left.Length as uint64
-    requires hi <= right.Length as uint64 && left != right
+    requires lo < hi <= left.Length
+    requires hi <= right.Length && left != right
     reads left, right
     modifies left, right
     ensures !where.Either? ==> where == resultPlacement
@@ -411,42 +537,51 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
 
     ghost var beforeWork := multiset(left[lo..hi]);
     var mid :=  ((hi - lo)/2) + lo;
-    var placement? := BoundedMergeSortMethod(left, right, lessThanOrEq, lo, mid, Either);
+    var placement? := NatMergeSortMethod(left, right, lessThanOrEq, lo, mid, Either);
     assert left[mid..hi] == old(left[mid..hi]);
-    ghost var placement2? := BoundedMergeSortMethod(left, right, lessThanOrEq, mid, hi, placement?);
+    ghost var placement2? := NatMergeSortMethod(left, right, lessThanOrEq, mid, hi, placement?);
     assert placement2? == placement?;
 
     ghost var preMergeResult := if placement?.Left? then left else right;
     calc {
       multiset(preMergeResult[lo..hi]);
-    == { assert preMergeResult[lo..hi] == preMergeResult[lo..mid] + preMergeResult[mid..hi]; }
+    == { LemmaSplitAt(preMergeResult[..], lo as nat, mid as nat, hi as nat); }
       multiset(preMergeResult[lo..mid] + preMergeResult[mid..hi]);
     ==
       multiset(old(left[lo..mid]) + old(left[mid..hi]));
-    ==  { assert old(left[lo..hi]) == old(left[lo..mid]) + old(left[mid..hi]); }
+    ==  { LemmaSplitAt(old(left[..]), lo as nat, mid as nat, hi as nat); }
       beforeWork;
     }
 
     ghost var mergedResult;
     if placement?.Left? {
-      BoundedMergeIntoRight(left := left, right := right, lessThanOrEq := lessThanOrEq, lo := lo, mid := mid, hi := hi);
+      NatMergeIntoRight(left := left, right := right, lessThanOrEq := lessThanOrEq, lo := lo, mid := mid, hi := hi);
       resultPlacement, mergedResult := Right, right[lo..hi];
 
-      assert left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..]);
+      assert left[hi..] == old(left[hi..]);
+      assert right[hi..] == old(right[hi..]);
+      assert left[..lo] == old(left[..lo]);
+      assert right[..lo] == old(right[..lo]);
     } else {
       assert placement?.Right?;
-      BoundedMergeIntoRight(left := right, right := left, lessThanOrEq := lessThanOrEq, lo := lo, mid := mid, hi := hi);
+      NatMergeIntoRight(left := right, right := left, lessThanOrEq := lessThanOrEq, lo := lo, mid := mid, hi := hi);
       resultPlacement, mergedResult := Left, left[lo..hi];
 
-      assert left[hi..] == old(left[hi..]) && right[hi..] == old(right[hi..]);
+      assert left[hi..] == old(left[hi..]);
+      assert right[hi..] == old(right[hi..]);
+      assert left[..lo] == old(left[..lo]);
+      assert right[..lo] == old(right[..lo]);
     }
 
     label BEFORE_RETURN:
-    assert left[..lo] == old(left[..lo]) && right[..lo] == old(right[..lo]);
+    assert left[hi..] == old(left[hi..]);
+    assert right[hi..] == old(right[hi..]);
+    assert left[..lo] == old(left[..lo]);
+    assert right[..lo] == old(right[..lo]);
     if resultPlacement.Left? && where == Right {
       // A forall comprehension might seem like a nice fit here,
       // however this does not good for two reasons.
-      // First, Dafny currently creates a range fur the full bounds of the bounded number
+      // First, Dafny currently creates a range for the full bounds of the bounded number
       // see: https://github.com/dafny-lang/dafny/issues/5897
       // Second this would create two loops.
       // First loop would create the `lo to hi` range of numbers.
@@ -459,6 +594,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
         invariant right[lo..i] == left[lo..i]
       {
         right[i] := left[i];
+        assert right[lo..i] == left[lo..i];
       }
 
       assert right[lo..hi] == mergedResult by {
@@ -477,6 +613,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
         invariant left[lo..i] == right[lo..i]
       {
         left[i] := right[i];
+        assert right[lo..i] == left[lo..i];
       }
 
       assert left[lo..hi] == mergedResult by {
@@ -489,26 +626,24 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     }
   }
 
-  method {:isolate_assertions} BoundedMergeIntoRight<T(!new)>(
+  method {:isolate_assertions} NatMergeIntoRight<T(!new)>(
     nameonly left: array<T>,
     nameonly right: array<T>,
     nameonly lessThanOrEq: (T, T) -> bool,
-    nameonly lo: uint64,
-    nameonly mid: uint64,
-    nameonly hi: uint64
+    nameonly lo: nat,
+    nameonly mid: nat,
+    nameonly hi: nat
   )
     requires Relations.TotalOrdering(lessThanOrEq)
-    requires
-      && left.Length < UINT64_LIMIT
-      && right.Length < UINT64_LIMIT
-    requires lo <= mid <= hi <= left.Length as uint64
-    requires  hi <= right.Length as uint64 && left != right
+    requires lo <= mid <= hi <= left.Length
+    requires  hi <= right.Length && left != right
     // We store "left" in [lo..mid]
     requires Relations.SortedBy(left[lo..mid], lessThanOrEq)
     // We store "right" in [mid..hi]
     requires Relations.SortedBy(left[mid..hi], lessThanOrEq)
-    reads left, right
+    // reads left, right
     modifies right
+    reads left, right
     // We do not modify anything before lo
     ensures right[..lo] == old(right[..lo]) && left[..lo] == old(left[..lo])
     // We do not modify anything above hi
@@ -543,8 +678,18 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
 
         BelowIsTransitive(right[lo..iter], left[leftPosition..mid], lessThanOrEq);
 
-        assert 0 < |right[lo..iter]| && 0 < |left[rightPosition..hi]| ==> lessThanOrEq(right[lo..iter][|right[lo..iter]| - 1], left[rightPosition..hi][0]);
+        assert 0 < |right[lo..iter]| && 0 < |left[rightPosition..hi]| ==> lessThanOrEq(right[lo..iter][|right[lo..iter]| - 1], left[rightPosition..hi][0]) by {
+          if 0 == |right[lo..iter]| || 0 == |left[rightPosition..hi]| {
+          } else {
+            assert Relations.SortedBy(left[oldRightPosition..hi], lessThanOrEq);
+            assert lessThanOrEq(left[oldRightPosition..hi][0], left[oldRightPosition..hi][1]);
+          }
+        }
         BelowIsTransitive(right[lo..iter], left[rightPosition..hi], lessThanOrEq);
+
+        assert multiset(right[lo..iter]) == multiset(left[lo..leftPosition] + left[mid..rightPosition]) by {
+          // Dafny just wants to be reminded
+        }
       } else {
         right[iter] := left[leftPosition];
 
@@ -554,17 +699,30 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
         assert 0 < |right[lo..iter]| && 0 < |left[leftPosition..mid]| ==> lessThanOrEq(right[lo..iter][|right[lo..iter]| - 1], left[leftPosition..mid][0]) by {
           if 0 == |right[lo..iter]| || 0 == |left[leftPosition..mid]| {
           } else {
-            assert right[lo..iter][|right[lo..iter]| - 1] == right[iter - 1];
-            assert left[leftPosition..mid][0] == left[leftPosition];
+            assert Relations.SortedBy(left[oldLeftPosition..mid], lessThanOrEq);
+            assert lessThanOrEq(left[oldLeftPosition..mid][0], left[oldLeftPosition..mid][1]);
           }
         }
         BelowIsTransitive(right[lo..iter], left[leftPosition..mid], lessThanOrEq);
+        assert 0 < |right[lo..iter]| && 0 < |left[rightPosition..hi]| ==> lessThanOrEq(right[lo..iter][|right[lo..iter]| - 1], left[rightPosition..hi][0]) by {
+          if 0 == |right[lo..iter]| || 0 == |left[rightPosition..hi]| {
+          } else {
+            assert right[lo..iter][|right[lo..iter]| - 1] == right[iter - 1];
+            assert left[rightPosition..hi][0] == left[rightPosition];
+          }
+        }
         BelowIsTransitive(right[lo..iter], left[rightPosition..hi], lessThanOrEq);
+
+        assert multiset(right[lo..iter]) == multiset(left[lo..leftPosition] + left[mid..rightPosition]) by {
+          // Dafny just wants to be reminded
+        }
       }
     }
     assert multiset(right[lo..hi]) == multiset(old(left[lo..hi])) by {
       assert leftPosition == mid && rightPosition == hi;
+      LemmaSplitAt(left[..], lo as nat, mid as nat, hi as nat);
       assert old(left[lo..hi]) == left[lo..hi] == left[lo..mid] + left[mid..hi];
     }
   }
+
 }
diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/SortCanon.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/SortCanon.dfy
index ae86e268b..f4a5fbde2 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/SortCanon.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/SortCanon.dfy
@@ -311,7 +311,7 @@ module SortCanon {
     ret
   } by method {
     AuthBelowIsTotal();
-    result := OptimizedMergeSort.FastMergeSort(x, AuthBelow);
+    result := OptimizedMergeSort.MergeSortNat(x, AuthBelow);
     CanonAuthListMultiNoDup(x, result);
     assert CanonAuthListHasNoDuplicates(result);
   }
@@ -331,7 +331,7 @@ module SortCanon {
     ret
   } by method {
     CryptoBelowIsTotal();
-    result := OptimizedMergeSort.FastMergeSort(x, CryptoBelow);
+    result := OptimizedMergeSort.MergeSortNat(x, CryptoBelow);
     CanonCryptoListMultiNoDup(x, result);
     assert CanonCryptoListHasNoDuplicates(result);
   }

From 286e7c7a46bf6f08013eab6655ecb43bb2535341 Mon Sep 17 00:00:00 2001
From: seebees <ryanemer@amazon.com>
Date: Wed, 12 Feb 2025 15:30:43 -0800
Subject: [PATCH 17/26] reads on methods not enabled

---
 .../StructuredEncryption/src/OptimizedMergeSort.dfy      | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
index f85b5cb82..2ed337996 100644
--- a/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
+++ b/DynamoDbEncryption/dafny/StructuredEncryption/src/OptimizedMergeSort.dfy
@@ -188,7 +188,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     requires lo < hi <= left.Length as BoundedInts.uint64
     requires hi <= right.Length as BoundedInts.uint64
     requires left != right
-    reads left, right
+    // reads left, right
     modifies left, right
     ensures !where.Either? ==> where == resultPlacement
 
@@ -321,7 +321,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     requires Relations.SortedBy(left[lo..mid], lessThanOrEq)
     // We store "right" in [mid..hi]
     requires Relations.SortedBy(left[mid..hi], lessThanOrEq)
-    reads left, right
+    // reads left, right
     modifies right
     // We do not modify anything before lo
     ensures right[..lo] == old(right[..lo]) && left[..lo] == old(left[..lo])
@@ -510,7 +510,7 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     requires Relations.TotalOrdering(lessThanOrEq)
     requires lo < hi <= left.Length
     requires hi <= right.Length && left != right
-    reads left, right
+    // reads left, right
     modifies left, right
     ensures !where.Either? ==> where == resultPlacement
 
@@ -641,9 +641,8 @@ module {:options "-functionSyntax:4"} OptimizedMergeSort {
     requires Relations.SortedBy(left[lo..mid], lessThanOrEq)
     // We store "right" in [mid..hi]
     requires Relations.SortedBy(left[mid..hi], lessThanOrEq)
-    // reads left, right
     modifies right
-    reads left, right
+    // reads left, right
     // We do not modify anything before lo
     ensures right[..lo] == old(right[..lo]) && left[..lo] == old(left[..lo])
     // We do not modify anything above hi

From 6118d8a554a677ae533ee586362bcfed80607512 Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Thu, 13 Feb 2025 10:39:00 -0500
Subject: [PATCH 18/26] chore(dafny): add timing support

---
 .../dafny/DDBEncryption/src/DecryptManifest.dfy       | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy b/TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy
index 5ee680b4e..61a325668 100644
--- a/TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy
+++ b/TestVectors/dafny/DDBEncryption/src/DecryptManifest.dfy
@@ -21,6 +21,7 @@ module {:options "-functionSyntax:4"} DecryptManifest {
   import JsonConfig
   import ENC = AwsCryptographyDbEncryptionSdkDynamoDbItemEncryptorTypes
   import KeyVectors
+  import OsLang
 
   method OnePositiveTest(name : string, config : JSON, encrypted : JSON, plaintext : JSON, keys : KeyVectors.KeyVectorsClient)
     returns (output : Result<bool, string>)
@@ -110,6 +111,14 @@ module {:options "-functionSyntax:4"} DecryptManifest {
     }
   }
 
+  function LogFileName() : string
+  {
+    if OsLang.GetOsShort() == "Windows" && OsLang.GetLanguageShort() == "Dotnet" then
+      "..\\..\\PerfLog.txt"
+    else
+      "../../PerfLog.txt"
+  }
+
   method Decrypt(inFile : string, keyVectors: KeyVectors.KeyVectorsClient)
     returns (output : Result<bool, string>)
     requires keyVectors.ValidState()
@@ -168,11 +177,13 @@ module {:options "-functionSyntax:4"} DecryptManifest {
       }
     }
 
+    var time := Time.GetAbsoluteTime();
     for i := 0 to |tests.value| {
       var obj := tests.value[i];
       :- Need(obj.1.Object?, "Value of test '" + obj.0 + "' must be an Object.");
       var _ :- OneTest(obj.0, obj.1, keyVectors);
     }
+    Time.PrintTimeSinceLong(time, "DB-ESDK-TV-Decrypt-" + inFile, Some(LogFileName()));
 
     timeStamp :- expect Time.GetCurrentTimeStamp();
     print timeStamp + " Tests Complete.\n";

From 13bb0c8081b749a788d8d60468e9d50204c66688 Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Thu, 13 Feb 2025 10:39:31 -0500
Subject: [PATCH 19/26] chore(dafny): add timing support

---
 submodules/MaterialProviders | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/submodules/MaterialProviders b/submodules/MaterialProviders
index 70e580991..c503da828 160000
--- a/submodules/MaterialProviders
+++ b/submodules/MaterialProviders
@@ -1 +1 @@
-Subproject commit 70e580991678387ce897a286c4f7f449aa616785
+Subproject commit c503da8285a510b25ace619b2cc27ffa247840ee

From b8915c53c9a4d2449f8e95267a35ae92866e70a7 Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Fri, 14 Mar 2025 12:11:03 -0400
Subject: [PATCH 20/26] m

---
 submodules/MaterialProviders | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/submodules/MaterialProviders b/submodules/MaterialProviders
index d5c4a20d9..841e263e0 160000
--- a/submodules/MaterialProviders
+++ b/submodules/MaterialProviders
@@ -1 +1 @@
-Subproject commit d5c4a20d97d5e074f874d672f22cef996b2c5079
+Subproject commit 841e263e0a3a17ce99d33b02cda27597e32fc86a

From 04641d218720e798476d3fc8aa68cdc26c6070b1 Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Tue, 25 Mar 2025 10:07:10 -0400
Subject: [PATCH 21/26] m

---
 submodules/MaterialProviders | 2 +-
 submodules/smithy-dafny      | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/submodules/MaterialProviders b/submodules/MaterialProviders
index 5ef166374..5ac41210f 160000
--- a/submodules/MaterialProviders
+++ b/submodules/MaterialProviders
@@ -1 +1 @@
-Subproject commit 5ef166374997984fe34e1ff28d5589ad1f2573d8
+Subproject commit 5ac41210f9b933a105a4eb6443a920a348453f34
diff --git a/submodules/smithy-dafny b/submodules/smithy-dafny
index 5fb3f25ea..b82c09eca 160000
--- a/submodules/smithy-dafny
+++ b/submodules/smithy-dafny
@@ -1 +1 @@
-Subproject commit 5fb3f25ea3444c51b2ad30b25ab03964cf866cd1
+Subproject commit b82c09eca754d242d1c50281b76aed8d687f9f93

From 6420c5a1da91fba343add7cf4292b2694bff9cce Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Tue, 25 Mar 2025 10:23:39 -0400
Subject: [PATCH 22/26] m

---
 submodules/MaterialProviders | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/submodules/MaterialProviders b/submodules/MaterialProviders
index 5ac41210f..977f43119 160000
--- a/submodules/MaterialProviders
+++ b/submodules/MaterialProviders
@@ -1 +1 @@
-Subproject commit 5ac41210f9b933a105a4eb6443a920a348453f34
+Subproject commit 977f431198e84ac7c9b2efa460f37596d5e350c4

From 06c119532a5ea094f725381fab7526cb6e3ec780 Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Fri, 28 Mar 2025 11:18:25 -0400
Subject: [PATCH 23/26] m

---
 .../test/DynamoDbGetEncryptedDataKeyDescriptionTest.dfy       | 4 ++--
 submodules/MaterialProviders                                  | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbGetEncryptedDataKeyDescriptionTest.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbGetEncryptedDataKeyDescriptionTest.dfy
index 91633e3db..ee74a7383 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbGetEncryptedDataKeyDescriptionTest.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbGetEncryptedDataKeyDescriptionTest.dfy
@@ -24,7 +24,7 @@ module DynamoDbGetEncryptedDataKeyDescriptionTest {
     assert s == UTF8.EncodeAscii("def");
     s
 
-  const awskms : UTF8.ValidUTF8Bytes :=
+  const aws_kms : UTF8.ValidUTF8Bytes :=
     var s := [0x61, 0x77, 0x73, 0x2d, 0x6b, 0x6d, 0x73];
     assert s == UTF8.EncodeAscii("aws-kms");
     s
@@ -57,7 +57,7 @@ module DynamoDbGetEncryptedDataKeyDescriptionTest {
   const testLegend : Legend := [0x65, 0x73]
   const testEncContext : CMPEncryptionContext := map[abc := def]
   const testAwsKmsDataKey := CMP.EncryptedDataKey(
-                               keyProviderId := awskms ,
+                               keyProviderId := aws_kms ,
                                keyProviderInfo := keyproviderInfo,
                                ciphertext := [1, 2, 3, 4, 5])
   const testAwsKmsHDataKey := CMP.EncryptedDataKey(
diff --git a/submodules/MaterialProviders b/submodules/MaterialProviders
index 977f43119..ab521336e 160000
--- a/submodules/MaterialProviders
+++ b/submodules/MaterialProviders
@@ -1 +1 @@
-Subproject commit 977f431198e84ac7c9b2efa460f37596d5e350c4
+Subproject commit ab521336ea59c6df2430b87519b3c931e967ed30

From 14a43111cd21e900ed76c0aaf8640b1ac3666bba Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Tue, 8 Apr 2025 11:08:27 -0400
Subject: [PATCH 24/26] m

---
 .../test/DynamoDbEncryptionBranchKeyIdSupplierTest.dfy        | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbEncryptionBranchKeyIdSupplierTest.dfy b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbEncryptionBranchKeyIdSupplierTest.dfy
index 5117ac892..43585a0e8 100644
--- a/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbEncryptionBranchKeyIdSupplierTest.dfy
+++ b/DynamoDbEncryption/dafny/DynamoDbEncryption/test/DynamoDbEncryptionBranchKeyIdSupplierTest.dfy
@@ -29,8 +29,8 @@ module DynamoDbEncryptionBranchKeyIdSupplierTest {
   const BRANCH_KEY_ID := "3f43a9af-08c5-4317-b694-3d3e883dcaef"
 
   const BRANCH_KEY_ID_UTF8 : UTF8.ValidUTF8Bytes :=
-    var s := [0x37, 0x35, 0x37, 0x38, 0x39, 0x31, 0x31, 0x35, 0x2d, 0x31, 0x64, 0x65, 0x62, 0x2d, 0x34, 0x66, 0x65, 0x33, 0x2d, 0x61, 0x32, 0x65, 0x63, 0x2d, 0x62, 0x65, 0x39, 0x65, 0x38, 0x38, 0x35, 0x64, 0x31, 0x39, 0x34, 0x35];
-    assert s == UTF8.EncodeAscii(BRANCH_KEY_ID);
+    var s := [0x33, 0x66, 0x34, 0x33, 0x61, 0x39, 0x61, 0x66, 0x2d, 0x30, 0x38, 0x63, 0x35, 0x2d, 0x34, 0x33, 0x31, 0x37, 0x2d, 0x62, 0x36, 0x39, 0x34, 0x2d, 0x33, 0x64, 0x33, 0x65, 0x38, 0x38, 0x33, 0x64, 0x63, 0x61, 0x65, 0x66];
+    assert s == UTF8.EncodeAscii("3f43a9af-08c5-4317-b694-3d3e883dcaef");
     s
 
   const ALTERNATE_BRANCH_KEY_ID := "4bb57643-07c1-419e-92ad-0df0df149d7c"

From cf528f53c7dd1a6759cf33914f7264d9b157ec81 Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Wed, 9 Apr 2025 13:57:12 -0400
Subject: [PATCH 25/26] m

---
 submodules/MaterialProviders | 2 +-
 submodules/smithy-dafny      | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/submodules/MaterialProviders b/submodules/MaterialProviders
index 5e6458921..d808fd88d 160000
--- a/submodules/MaterialProviders
+++ b/submodules/MaterialProviders
@@ -1 +1 @@
-Subproject commit 5e64589215eb4cf9337f5d4fac24a815d0f28831
+Subproject commit d808fd88df92acf64fb9ca0e7c652d72e05fa50a
diff --git a/submodules/smithy-dafny b/submodules/smithy-dafny
index b82c09eca..72f38c32d 160000
--- a/submodules/smithy-dafny
+++ b/submodules/smithy-dafny
@@ -1 +1 @@
-Subproject commit b82c09eca754d242d1c50281b76aed8d687f9f93
+Subproject commit 72f38c32dab1cc3d5b12db959e738dc4d46e950f

From dc551b2b99fc74ef394d5c67609afc106827b21f Mon Sep 17 00:00:00 2001
From: Andy Jewell <ajewell@amazon.com>
Date: Mon, 14 Apr 2025 16:19:29 -0400
Subject: [PATCH 26/26] m

---
 DynamoDbEncryption/runtimes/rust/Cargo.toml | 18 +++++++++---------
 TestVectors/runtimes/rust/Cargo.toml        |  2 +-
 submodules/MaterialProviders                |  2 +-
 submodules/smithy-dafny                     |  2 +-
 4 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/DynamoDbEncryption/runtimes/rust/Cargo.toml b/DynamoDbEncryption/runtimes/rust/Cargo.toml
index cdad092d6..a5877c06d 100644
--- a/DynamoDbEncryption/runtimes/rust/Cargo.toml
+++ b/DynamoDbEncryption/runtimes/rust/Cargo.toml
@@ -16,20 +16,20 @@ readme = "README.md"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-aws-config = "1.5.15"
+aws-config = "1.6.0"
 aws-lc-rs = "1.12.2"
 aws-lc-sys = "0.25.0"
-aws-sdk-dynamodb = "1.62.0"
-aws-sdk-kms = "1.57.0"
-aws-smithy-runtime-api = {version = "1.7.3", features = ["client"] }
-aws-smithy-types = "1.2.12"
-chrono = "0.4.39"
+aws-sdk-dynamodb = "1.69.0"
+aws-sdk-kms = "1.63.0"
+aws-smithy-runtime-api = {version = "1.7.4", features = ["client"] }
+aws-smithy-types = "1.3.0"
+chrono = "0.4.40"
 cpu-time = "1.0.0"
-dafny_runtime = { path = "../../../submodules/smithy-dafny/TestModels/dafny-dependencies/dafny_runtime_rust", features = ["sync"] }
+dafny_runtime = { path = "../../../submodules/smithy-dafny/TestModels/dafny-dependencies/dafny_runtime_rust", features = ["sync","small-int"] }
 dashmap = "6.1.0"
 pem = "3.0.4"
-tokio = {version = "1.43.0", features = ["full"] }
-uuid = { version = "1.12.1", features = ["v4"] }
+tokio = {version = "1.44.1", features = ["full"] }
+uuid = { version = "1.16.0", features = ["v4"] }
 
 [[example]]
 name = "main"
diff --git a/TestVectors/runtimes/rust/Cargo.toml b/TestVectors/runtimes/rust/Cargo.toml
index 3b632b1d7..aad1d58f9 100644
--- a/TestVectors/runtimes/rust/Cargo.toml
+++ b/TestVectors/runtimes/rust/Cargo.toml
@@ -16,7 +16,7 @@ aws-smithy-runtime-api = {version = "1.7.3", features = ["client"] }
 aws-smithy-types = "1.2.12"
 chrono = "0.4.39"
 cpu-time = "1.0.0"
-dafny_runtime = { path = "../../../submodules/smithy-dafny/TestModels/dafny-dependencies/dafny_runtime_rust", features = ["sync"] }
+dafny_runtime = { path = "../../../submodules/smithy-dafny/TestModels/dafny-dependencies/dafny_runtime_rust", features = ["sync","small-int"] }
 dashmap = "6.1.0"
 pem = "3.0.4"
 tokio = {version = "1.43.0", features = ["full"] }
diff --git a/submodules/MaterialProviders b/submodules/MaterialProviders
index d808fd88d..515995e68 160000
--- a/submodules/MaterialProviders
+++ b/submodules/MaterialProviders
@@ -1 +1 @@
-Subproject commit d808fd88df92acf64fb9ca0e7c652d72e05fa50a
+Subproject commit 515995e68400e56fc720412fe355e6965715136e
diff --git a/submodules/smithy-dafny b/submodules/smithy-dafny
index 72f38c32d..2f83e28ad 160000
--- a/submodules/smithy-dafny
+++ b/submodules/smithy-dafny
@@ -1 +1 @@
-Subproject commit 72f38c32dab1cc3d5b12db959e738dc4d46e950f
+Subproject commit 2f83e28ad9532b24c93d2229476c9a268355d338