token_unseal: wield three unsealing authorities
diff --git a/sdk/core/loader/boot.cc b/sdk/core/loader/boot.cc
index 9aabbb0..bc67535 100644
--- a/sdk/core/loader/boot.cc
+++ b/sdk/core/loader/boot.cc
@@ -1267,15 +1267,31 @@
 	              sizeof(void *),
 	              PermissionSet{Permission::Global, Permission::Unseal});
 
-	setSealingKey(imgHdr.allocator(), Allocator);
+	/*
+	 * The token library unseals both static and dynamic objects, sometimes
+	 * either and sometimes with static knowledge of which is expected.  To
+	 * avoid `li; csetaddr; csetbounds` sequences, we give it a separate cap
+	 * for each case.
+	 */
 	setSealingKey(imgHdr.token_library(),
 	              Allocator,
 	              2, // Allocator and StaticToken
 	              0,
 	              PermissionSet{Permission::Global, Permission::Unseal});
+	setSealingKey(imgHdr.token_library(),
+	              StaticToken,
+	              1,
+	              sizeof(void *),
+	              PermissionSet{Permission::Global, Permission::Unseal});
+	setSealingKey(imgHdr.token_library(),
+	              Allocator,
+	              1,
+	              2 * sizeof(void *),
+	              PermissionSet{Permission::Global, Permission::Unseal});
+
 	constexpr size_t DynamicSealingLength =
 	  std::numeric_limits<ptraddr_t>::max() - FirstDynamicSoftware + 1;
-
+	setSealingKey(imgHdr.allocator(), Allocator);
 	setSealingKey(imgHdr.allocator(),
 	              FirstDynamicSoftware,
 	              DynamicSealingLength,
diff --git a/sdk/core/token_library/token_unseal.S b/sdk/core/token_library/token_unseal.S
index 1dccc04..8528d72 100644
--- a/sdk/core/token_library/token_unseal.S
+++ b/sdk/core/token_library/token_unseal.S
@@ -2,15 +2,34 @@
 #include <cheri-builtins.h>
 #include "../allocator/token.h"
 
-	.hidden __sealingkey
-	.type   __sealingkey,@object
-	.section    .sealing_key1,"aw",@progbits
-	.globl  __sealingkey
-	.p2align    3
-__sealingkey:
-	.chericap   0
-	.size   __sealingkey, 8
+.include "assembly-helpers.s"
 
+	.hidden __sealingkey_either
+	.type   __sealingkey_either,@object
+	.section    .sealing_key1,"aw",@progbits
+	.globl  __sealingkey_either
+	.p2align    3
+__sealingkey_either:
+	.chericap   0
+	.size   __sealingkey_either, 8
+
+	.hidden __sealingkey_static
+	.type   __sealingkey_static,@object
+	.section    .sealing_key2,"aw",@progbits
+	.globl  __sealingkey_static
+	.p2align    3
+__sealingkey_static:
+	.chericap   0
+	.size   __sealingkey_static, 8
+
+	.hidden __sealingkey_dynamic
+	.type   __sealingkey_dynamic,@object
+	.section    .sealing_key3,"aw",@progbits
+	.globl  __sealingkey_dynamic
+	.p2align    3
+__sealingkey_dynamic:
+	.chericap   0
+	.size   __sealingkey_dynamic, 8
 
 .section .text,"ax",@progbits
 
@@ -19,21 +38,20 @@
 /**
  * The core of unsealing:
  *
- *   void *token_unseal_internal(struct SKeyStruct *, struct SObjStruct *, int);
+ *   void *token_unseal_internal(
+ *     struct SKeyStruct *, struct SObjStruct *, void *);
  */
-
 .Ltoken_unseal_internal:
   /*
    * Register allocation:
    *
-   *  - ca0 holds a sealing key, either the user's or the real deal, and is
-   *    replaced with the unsealed value or NULL
+   *  - ca0 holds the user's sealing key, and is replaced with the unsealed
+   *    value or NULL
    *
    *  - ca1 holds the user's sealed object pointer
    *
-   *  - a2 contains the expected sealing type.
-   *
-   *  - t0 holds a copy of the user key's address field (authorized type)
+   *  - ca2 holds the unsealing authority and is clobbered on failure
+   *    explicitly and on success with a scalar (the sealed payload's length)
    *
    *  - t1 is used within each local computation and never holds secrets
    */
@@ -53,53 +71,41 @@
   andi     t1, t1, CHERI_PERM_UNSEAL
   beqz     t1, .Lexit_failure
 
-  /* Copy key type to scratch register */
-  cgetaddr t0, ca0
-
-  /*
-   * Load unsealing root capability, to be clobbered by return value
-   * This faults only if something has gone very, very wrong, and exposes no
-   * secrets if so.
-   */
-.Lload_sealing_key:
-  auipcc ca0, %cheriot_compartment_hi(__sealingkey)
-  clc    ca0, %cheriot_compartment_lo_i(.Lload_sealing_key)(ca0)
-  csetaddr ca0, ca0, a2
-
   /* Unseal, clobbering authority */
-  cunseal ca0, ca1, ca0
+  cunseal ca2, ca1, ca2
 
   /* Verify tag of unsealed form */
-  cgettag t1, ca0
+  cgettag t1, ca2
   beqz    t1, .Lexit_failure
 
   /*
    * Load software type tag.  This will not trap, thanks to above tag check and
    * because IRQs are deferred (see our export entry below)
    */
-  clw t1, TokenSObj_offset_type(ca0)
+  clw t1, TokenSObj_offset_type(ca2)
 
   /* Verify that the loaded value matches the address of the key. */
-  bne t0, t1, .Lexit_failure
+  bne a0, t1, .Lexit_failure
 
   /* Subset bounds to ->data */
   // Get the top into t1
-  cgettop         t1, ca0
-  // Move the address to the start of the data
-  cincoffset      ca0, ca0, TokenSObj_offset_data
+  cgettop         t1, ca2
+  // Move the address to the start of the data, clobber the user's sealing key
+  cincoffset      ca0, ca2, TokenSObj_offset_data
   // Subtract the address of the (to-be-returned-unsealed) data from the top to
-  // give the length.
-  sub             t1, t1, a0
+  // give the length, clobbering our unsealing key.
+  sub             a2, t1, a0
   // Set the new bounds, using an exact setting so that any errors in the
   // allocator's alignment turn into an untagged capability here.
-  csetboundsexact ca0, ca0, t1
+  csetboundsexact ca0, ca0, a2
 
   /* And that's an unwrap. */
   cret
 
 .Lexit_failure:
-  /* Failure; clobber potential sensitive state in ca0 and return null */
-  cmove ca0, cnull
+  /* Failure; clobber potential sensitive state in ca2 and return null */
+  zeroOne a2
+  zeroOne a0
   cret
 
 /**
@@ -113,7 +119,16 @@
 	.hidden _Z16token_obj_unsealP10SKeyStructP10SObjStruct
 	.globl  _Z16token_obj_unsealP10SKeyStructP10SObjStruct
 _Z16token_obj_unsealP10SKeyStructP10SObjStruct:
-	cgettype a2, ca1
+	LoadCapPCC ca2, __sealingkey_either
+
+	/*
+	 * Backwards compatibility with CUnseal that requires address match.
+	 * This can (and should) be removed once everyone's caught up with
+	 * https://github.com/CHERIoT-Platform/cheriot-sail/pull/87 .
+	 */
+	cgettype   t1, ca1
+	csetaddr   ca2, ca2, t1
+
 	j        .Ltoken_unseal_internal
 
 /**
@@ -127,8 +142,8 @@
 	.hidden  _Z23token_obj_unseal_staticP10SKeyStructP10SObjStruct
 	.globl   _Z23token_obj_unseal_staticP10SKeyStructP10SObjStruct
 _Z23token_obj_unseal_staticP10SKeyStructP10SObjStruct:
-	li       a2, CheriSealTypeStaticToken
-	j        .Ltoken_unseal_internal
+	LoadCapPCC ca2, __sealingkey_static
+	j          .Ltoken_unseal_internal
 
 /**
  * An in-assembler implementation of
@@ -138,11 +153,11 @@
  *
  * The name has been manually mangled as per the C++ rules.
  */
-	.hidden  _Z16token_obj_unsealP10SKeyStructP10SObjStruct
-	.globl   _Z16token_obj_unsealP10SKeyStructP10SObjStruct
+	.hidden  _Z24token_obj_unseal_dynamicP10SKeyStructP10SObjStruct
+	.globl   _Z24token_obj_unseal_dynamicP10SKeyStructP10SObjStruct
 _Z24token_obj_unseal_dynamicP10SKeyStructP10SObjStruct:
-	li       a2, CheriSealTypeAllocator
-	j        .Ltoken_unseal_internal
+	LoadCapPCC ca2, __sealingkey_dynamic
+	j          .Ltoken_unseal_internal
 
 /* TODO: Eventually this goes away, when the assembler can generate it for us */
 CHERIOT_EXPORT_LIBCALL \