Network Working Group N. Sullivan Internet-Draft Cryptography Consulting LLC Intended status: Informational 2 November 2026 Expires: 6 May 2027 SEAL Cipher Suites and Instantiations draft-sullivan-seal-concrete-01 Abstract SEAL (Segmented Encryption and Authentication Layer) is a construction that realizes random-access authenticated encryption (raAE), a primitive that partitions a message into an indexed sequence of independently accessible, individually authenticated segments. This document specifies concrete, interoperable instantiations of SEAL for particular uses, built from previously specified primitives, and applies the analysis in a companion document to state their security properties and safe usage limits. Discussion Venues This note is to be removed before publishing as an RFC. Discussion of this document takes place on the raae mailing list (raae@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/raae/. Source for this draft and an issue tracker can be found at https://github.com/grittygrease/draft-sullivan-cfrg-raae. Status of This Memo This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79. Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet- Drafts is at https://datatracker.ietf.org/drafts/current/. Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress." This Internet-Draft will expire on 6 May 2027. Sullivan Expires 6 May 2027 [Page 1] Internet-Draft SEAL November 2026 Copyright Notice Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved. This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/ license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License. Table of Contents 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 4 2. Conventions and Terminology . . . . . . . . . . . . . . . . . 5 3. SEAL Suites . . . . . . . . . . . . . . . . . . . . . . . . . 5 3.1. Algorithms . . . . . . . . . . . . . . . . . . . . . . . 6 3.2. Composing a SEAL Suite . . . . . . . . . . . . . . . . . 9 3.2.1. Choosing a Profile . . . . . . . . . . . . . . . . . 11 4. Serialization Layouts . . . . . . . . . . . . . . . . . . . . 12 4.1. Linear Layout . . . . . . . . . . . . . . . . . . . . . . 13 4.2. Aligned Layout . . . . . . . . . . . . . . . . . . . . . 14 4.3. Split Layout . . . . . . . . . . . . . . . . . . . . . . 17 4.4. Append-Log Layout . . . . . . . . . . . . . . . . . . . . 18 4.5. Reduced Layouts under a Derived Nonce . . . . . . . . . . 19 4.6. Content-Defined Chunking (Reserved) . . . . . . . . . . . 20 5. Snapshot Authenticator Instantiations . . . . . . . . . . . . 20 5.1. Masked Multiset Hash . . . . . . . . . . . . . . . . . . 20 5.2. Digest Transcript . . . . . . . . . . . . . . . . . . . . 24 5.3. Epoch Digest Tree . . . . . . . . . . . . . . . . . . . . 28 5.3.1. Public and Intermediate Snapshots . . . . . . . . . . 34 6. Snapshot Authenticator Security . . . . . . . . . . . . . . . 36 6.1. Masked Multiset Hash . . . . . . . . . . . . . . . . . . 36 6.2. Digest Transcript . . . . . . . . . . . . . . . . . . . . 37 6.3. Epoch Digest Tree . . . . . . . . . . . . . . . . . . . . 40 7. SEAL Named Instantiations . . . . . . . . . . . . . . . . . . 42 8. Binding Limits for Profiled Suites . . . . . . . . . . . . . 45 8.1. Write Limits by Instantiation . . . . . . . . . . . . . . 47 9. Streaming with Prefix Verification . . . . . . . . . . . . . 49 10. Security Considerations . . . . . . . . . . . . . . . . . . . 50 10.1. Resource Exhaustion . . . . . . . . . . . . . . . . . . 51 11. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 51 11.1. Designated Expert Review . . . . . . . . . . . . . . . . 54 12. References . . . . . . . . . . . . . . . . . . . . . . . . . 55 12.1. Normative References . . . . . . . . . . . . . . . . . . 55 Sullivan Expires 6 May 2027 [Page 2] Internet-Draft SEAL November 2026 12.2. Informative References . . . . . . . . . . . . . . . . . 56 Appendix A. SEAL-simple Implementation Sketch . . . . . . . . . 57 A.1. A Modern CFRG Suite . . . . . . . . . . . . . . . . . . . 60 Appendix B. Snapshot Authenticator Reduction . . . . . . . . . . 62 Appendix C. Snapshot Authenticator Design Rationale . . . . . . 63 Appendix D. Test Vectors . . . . . . . . . . . . . . . . . . . . 65 D.1. Single Segment, AES-256-GCM, HKDF-SHA-256, 16384 . . . . 66 D.2. Commitment with Global Associated Data . . . . . . . . . 68 D.3. KDF Combiner Vectors . . . . . . . . . . . . . . . . . . 68 D.4. Single Segment, AES-256-GCM, HKDF-SHA-256, 65536 . . . . 70 D.5. Single Segment, ChaCha20-Poly1305, HKDF-SHA-256, 16384 . 71 D.6. Single Segment, ChaCha20-Poly1305, HKDF-SHA-256, 65536 . 73 D.7. Single Segment, AEGIS-256, HKDF-SHA-256, 16384 . . . . . 74 D.8. Single Segment, AEGIS-256, HKDF-SHA-256, 65536 . . . . . 75 D.9. Two Segment, AES-256-GCM, HKDF-SHA-256, 16384 . . . . . . 77 D.10. Two Segment, AES-256-GCM, HKDF-SHA-256, 65536 . . . . . . 79 D.11. Two Segment, ChaCha20-Poly1305, HKDF-SHA-256, 16384 . . . 80 D.12. Two Segment, ChaCha20-Poly1305, HKDF-SHA-256, 65536 . . . 82 D.13. Two Segment, AEGIS-256, HKDF-SHA-256, 16384 . . . . . . . 84 D.14. Two Segment, AEGIS-256, HKDF-SHA-256, 65536 . . . . . . . 85 D.15. TurboSHAKE-256 Cipher Suite Vectors . . . . . . . . . . . 87 D.15.1. Single Segment, AES-256-GCM, TurboSHAKE-256, 65536 . . . . . . . . . . . . . . . . . . . . . . . . 87 D.15.2. Two Segment, AES-256-GCM, TurboSHAKE-256, 65536 . . 89 D.16. Segment Rewrite Vector . . . . . . . . . . . . . . . . . 91 D.16.1. Two Segment, AES-256-GCM, HKDF-SHA-256, 65536 (Rewrite) . . . . . . . . . . . . . . . . . . . . . . 91 D.17. Derived-Nonce Cipher Suite Vector . . . . . . . . . . . . 94 D.17.1. Two Segment, AES-256-GCM-SIV, HKDF-SHA-256, 65536 (Rewrite) . . . . . . . . . . . . . . . . . . . . . . 94 D.18. Cross-Epoch Key Vector . . . . . . . . . . . . . . . . . 96 D.18.1. Two Segment, AES-256-GCM, HKDF-SHA-256, 65536, epoch_length 0 . . . . . . . . . . . . . . . . . . . 96 D.19. Plaintext-Bound Nonce Component Vectors . . . . . . . . . 98 D.19.1. AES-256-GCM, HKDF-SHA-256, 16384, Segment 0 . . . . 98 D.19.2. AES-256-GCM, TurboSHAKE-256, 65536, Segment 1 . . . 99 D.19.3. AES-256-GCM, HKDF-SHA-256, 16384, Non-Final Segment 0 with A_i . . . . . . . . . . . . . . . . . . . . . . 100 D.20. Digest Transcript Component Vectors . . . . . . . . . . . 101 D.20.1. Leaves and Transcript, n_seg = 2 . . . . . . . . . . 102 D.21. Epoch Digest Tree Component Vectors . . . . . . . . . . . 103 D.22. Negative SnapVerify Vector . . . . . . . . . . . . . . . 105 D.22.1. Tampered Segment Tag, snapshot Not Recomputed . . . 105 D.23. SEAL-simple(HKDF-SHA-256, AES-256-GCM) . . . . . . . . . 106 Appendix E. Changes Since -00 . . . . . . . . . . . . . . . . . 108 Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . 109 Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 109 Sullivan Expires 6 May 2027 [Page 3] Internet-Draft SEAL November 2026 1. Introduction SEAL (Segmented Encryption and Authentication Layer) is a construction that realizes random-access authenticated encryption (raAE). A SEAL object is an indexed sequence of segments, each encrypted and authenticated on its own, so a reader can decrypt any one segment without decrypting the rest. In a profile that permits it, a writer can re-encrypt one segment in place, append to the end, or truncate from the end. An object can also carry a keyed snapshot that binds the whole segment set as the writer recorded it. A SEAL scheme is parameterized by a cipher suite, a set of operational parameters, and a serialization layout. The immutable profile suits whole files, archives, and backups, whether read straight through or verified segment by segment against a snapshot that binds every segment. How far that binding reaches, and in particular whether it holds against a holder of the content key, depends on the snapshot authenticator, whether the ciphertext digest is set, and external authentication of the keyed snapshot (Section 6). The mutable profile suits objects edited in place, including memory-mapped files and disk encryption. The append-only profile suits growing archives and logs that are never rewritten. SEAL is specified across two documents. The companion ([I-D.sullivan-cfrg-raae]) defines the raAE primitive, specifies the SEAL construction and its algorithms, and gives the security analysis: reductions, bounds, and budgets. This document builds its concrete suites and instantiations on that construction and derives their safe-use limits from that analysis (Section 8). It fixes what two implementations must share to interoperate: the cipher suites, the serialization layouts, and the code points. An implementer needs both, and this document uses the companion's terminology and notation. A relying protocol that does not want to make every choice can cite one of the named instantiations (Section 7) and supply only a cipher suite, an AEAD algorithm paired with a KDF. The instantiation fixes the rest: the serialization layout, the snapshot authenticator, the ciphertext digest, the segment size, the nonce mode, and the epoch length, within one of the three profiles. The aead_id and kdf_id code points come from existing IANA registries, the AEAD Algorithms registry and the HPKE KDF registry. This document adds two registries of its own, one for the named instantiations and one for the snapshot authenticators (Section 11). Sullivan Expires 6 May 2027 [Page 4] Internet-Draft SEAL November 2026 2. Conventions and Terminology The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. This document uses the terminology and notation of the companion ([I-D.sullivan-cfrg-raae]) without restating them, including segment, object, commitment, snapshot authenticator, snap_id, profile, cipher suite, CEK, salt, epoch, the key-schedule and snapshot symbols (payload_info, payload_key, snap_key, LH, Np, and the per-segment leaf), the ciphertext digest, the witness, and the advantage notation used for the security bounds. ct_i and tag_i are the two parts of a segment's AEAD output under the companion's AEAD-specific split. Every suite in Table 1 places its tag in the final Nt octets of that output, so tag_i is those Nt octets and ct_i is every octet before them. In random nonce mode, the stored nonce is not part of ct_i or tag_i; the leaf carries it separately as nonce_field(i) ([I-D.sullivan-cfrg-raae]). aux: The aux region, the serialized summary of [I-D.sullivan-cfrg-raae] that a layout stores outside the keyed snapshot and the per-segment metadata entries. It is untrusted: a reader checks it only through the keyed snapshot, as part of a witness. Its contents are fixed by the authenticator: none under the null snapshot and the digest transcript; the masked accumulator, Nh octets, under the masked multiset hash; and the keyed epoch heads d_0 through d_{n_ep-1}, in epoch order, n_ep * Nh octets, under the epoch digest tree. Run: The octet string a snapshot value hashes with LH: the encoded leaves of the whole object (transcript_run), the encoded leaves of one epoch (epoch_run(e)), or the concatenated epoch heads. Each leaf in a run is framed by encode; heads are concatenated, since each is Nh octets. 3. SEAL Suites The suites below are examples for the SEAL construction, whose concrete framing and procedures are defined in [I-D.sullivan-cfrg-raae]. Sullivan Expires 6 May 2027 [Page 5] Internet-Draft SEAL November 2026 3.1. Algorithms SEAL defines the following AEAD algorithms. For each, the table lists its code point and the sizes the construction takes from the AEAD ([I-D.sullivan-cfrg-raae]): the key size Nk, the nonce size Nn, the tag size Nt, and the cipher-block size Nb, in octets. Nb is written n/a where no block-size limit applies: for a stream cipher, or for an AEAD whose analysis binds on forgery instead. The MRAE column marks whether the AEAD is misuse-resistant. Only a misuse- resistant AEAD may be paired with a derived nonce under rewrite (Section 3.2). +===================+=========+==+==+====+=====+======+============+ | Algorithm | aead_id |Nk|Nn| Nt | Nb | MRAE | committing | +===================+=========+==+==+====+=====+======+============+ | AES-128-GCM | 0x0001 |16|12| 16 | 16 | no | no | +-------------------+---------+--+--+----+-----+------+------------+ | AES-256-GCM | 0x0002 |32|12| 16 | 16 | no | no | +-------------------+---------+--+--+----+-----+------+------------+ | ChaCha20-Poly1305 | 0x001D |32|12| 16 | n/a | no | no | +-------------------+---------+--+--+----+-----+------+------------+ | AES-256-GCM-SIV | 0x001F |32|12| 16 | 16 | yes | no | +-------------------+---------+--+--+----+-----+------+------------+ | AEGIS-256 | 0x0021 |32|32| 16 | n/a | no | no | +-------------------+---------+--+--+----+-----+------+------------+ | AEGIS-256X2 | 0x0024 |32|32| 16 | n/a | no | no | +-------------------+---------+--+--+----+-----+------+------------+ Table 1: SEAL AEAD algorithms and their sizes The aead_id values are the unsigned 16-bit code points from the IANA AEAD Algorithms Registry ([RFC5116]), encoded as uint16(id). The registry's textual identifiers are, in table order, AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_CHACHA20_POLY1305, AEAD_AES_256_GCM_SIV, AEAD_AEGIS256, and AEAD_AEGIS256X2. AES-128-GCM and AES-256-GCM are specified in [NIST-SP-800-38D], ChaCha20-Poly1305 in [RFC8439], AES- 256-GCM-SIV in [RFC8452], and AEGIS-256 and AEGIS-256X2 in [RFC10032]. The AEGIS code points 0x0021 and 0x0024 are registered in the AEAD Algorithms registry. The nonce mode and epoch_length are not properties of the AEAD. A profile and the referencing protocol set them (Section 3.2, Section 7), within the per-suite budgets of [I-D.sullivan-cfrg-raae]. The append-only and immutable profiles accept any AEAD, because their writer disciplines keep every derived nonce unique (Section 3.2). Sullivan Expires 6 May 2027 [Page 6] Internet-Draft SEAL November 2026 For ChaCha20-Poly1305, random nonce mode departs from [RFC8439] Section 2.6, which requires the nonce to be unique per invocation with the same key and so forbids generating it at random. SEAL relies instead on the birthday bound of [I-D.sullivan-cfrg-raae]: within the random-nonce budget of Table 11, about 2^32 encryptions per epoch key, a nonce repeats under that key with probability at most 2^-32. A repeated nonce reproduces both the keystream and the one-time Poly1305 key, so it costs authentication as well as confidentiality for that nonce ([RFC8439] Section 4). No SEAL AEAD is natively committing (the committing column). An instantiation that must bind its segments against a CEK holder sets the ciphertext digest ([I-D.sullivan-cfrg-raae]), so each segment's leaf carries LH(ct_i). The commitment is a payload_info choice independent of snap_id. Content binding against a CEK holder needs it together with a strict authenticator (snap_id 0x0002 or 0x0003, Section 11) and a snapshot authenticated outside the construction, and a linear authenticator MUST NOT set it (Section 6). Only the attachment instantiations (Section 7) set it. An AEGIS profile that a shared-object protocol consumes SHOULD set it too, since AEGIS's 128-bit tag does not bind its segments ([I-D.sullivan-cfrg-raae]). These conditions bind ciphertext bytes. The strict authenticators' leaf also carries each segment's nonce field and segment additional authenticated data (AAD). The strict authenticators therefore fix each index's nonce and AAD in either nonce mode and give per-index binding for every suite (Section 6.2). SEAL permits the KDF cipher suites in Table 2, identified by entries from the HPKE KDF Registry ([RFC9180] Section 7.2 and [I-D.ietf-hpke-pq]): +========+================+===============================+====+ | kdf_id | Name | Construction | Nh | +========+================+===============================+====+ | 0x0001 | HKDF-SHA-256 | Two-step HKDF-Extract+Expand | 32 | +--------+----------------+-------------------------------+----+ | 0x0002 | HKDF-SHA-384 | Two-step HKDF-Extract+Expand | 48 | +--------+----------------+-------------------------------+----+ | 0x0003 | HKDF-SHA-512 | Two-step HKDF-Extract+Expand | 64 | +--------+----------------+-------------------------------+----+ | 0x0013 | TurboSHAKE-256 | One-step XOF (absorb+squeeze) | 64 | +--------+----------------+-------------------------------+----+ Table 2: Permitted SEAL KDF Cipher Suites Sullivan Expires 6 May 2027 [Page 7] Internet-Draft SEAL November 2026 HKDF-SHA-256 is specified in [RFC5869]. TurboSHAKE-256 is the extendable-output mode of the Keccak-p permutation family, specified in [RFC9861] and selected by [I-D.ietf-hpke-pq] for the HPKE KDF Registry. The kdf_id values are the unsigned 16-bit code points from the HPKE KDF Registry, encoded as uint16(id). Nh and commitment_length equal the KDF's primitive output size, 32 octets for HKDF-SHA-256, 48 for HKDF-SHA-384, and 64 for HKDF-SHA-512 and TurboSHAKE-256. HKDF-SHA-256 is the baseline KDF. The others are optional alternatives. The two-step Extract and Expand are HKDF-Extract and HKDF-Expand. The one-step XOF uses TurboSHAKE256 with D = 0x1F, the Derive convention's domain-separator byte, which an implementation MUST NOT change. The snapshot authenticator is selected by snap_id: +=========+======================+=================================+ | snap_id | Name | Values produced | +=========+======================+=================================+ | 0x0000 | none | none produced | +---------+----------------------+---------------------------------+ | 0x0001 | masked multiset hash | keyed snapshot, per Section 5.1 | +---------+----------------------+---------------------------------+ | 0x0002 | digest transcript | keyed snapshot and public | | | | snapshot, per Section 5.2 | +---------+----------------------+---------------------------------+ | 0x0003 | epoch digest tree | keyed snapshot and public | | | | snapshot, per Section 5.3 | +---------+----------------------+---------------------------------+ Table 3: SEAL Snapshot Authenticators snap_id 0x0000 selects no snapshot authenticator, a null snapshot: no keyed snapshot is produced (Na = 0) and a reader relies on per- segment authentication alone. A null snapshot is not the same as a snapshot taken over an empty segment set, itself a produced value. Which snap_id values each profile admits, and the rewrite cost of each authenticator, are set in Section 3.2 and Section 3.2.1. The nonce mode is carried explicitly by nonce_mode: Sullivan Expires 6 May 2027 [Page 8] Internet-Draft SEAL November 2026 +============+=========+ | nonce_mode | Name | +============+=========+ | 0x00 | random | +------------+---------+ | 0x01 | derived | +------------+---------+ Table 4: SEAL Nonce Modes The two constructions are defined in [I-D.sullivan-cfrg-raae]. The supported maximum segment sizes are 16384 and 65536 octets. Both values are powers of two and at least 4096 octets. The 16384-octet size aligns to 16 KiB memory pages (for example, on Apple Silicon). The 65536-octet size aligns to 64 KiB (four 16 KiB pages). The aad_label is "SEAL-DATA". 3.2. Composing a SEAL Suite A SEAL suite fixes an AEAD and a KDF (Table 1, Table 2), a maximum segment size, a snapshot authenticator (snap_id, Table 3), a nonce mode (nonce_mode, Table 4), and an epoch length. The protocol_id identifies the parameters a profile fixes, and only certain (nonce_mode, snap_id) tuples are valid under each one. This document defines three named profiles, SEAL-RW-v1, SEAL-AO-v1, and SEAL-RO-v1. The profile names describe the writer's discipline, not tamper evidence. A profile's payload_info MUST carry the full parameter context affecting key derivation, AEAD operations, AAD construction, and nonce construction, so that the commitment ([I-D.sullivan-cfrg-raae]) binds that context. SEAL-RW-v1 is the mutable profile (read-write). It requires a snapshot authenticator, so it admits any snap_id but a null snapshot, which among the authenticators defined here is 0x0001 (the masked multiset hash), 0x0002 (the digest transcript), or 0x0003 (the epoch digest tree), and it permits a random nonce or a derived nonce with an MRAE AEAD. It supports rewrite, extend, and truncate. SEAL-AO-v1 is the append-only profile. Because its derived nonce repeats on re-encryption ([I-D.sullivan-cfrg-raae]), an encryptor under SEAL-AO-v1 MUST NOT rewrite a segment with a different plaintext or associated data, and MUST NOT truncate. It changes length by the append direction of [I-D.sullivan-cfrg-raae] alone, whose re-mark carries the segment's existing plaintext and changes Sullivan Expires 6 May 2027 [Page 9] Internet-Draft SEAL November 2026 only the finality bit. The profile admits any snap_id, and the choice does not affect the append-only discipline. A protocol that needs rewrites or truncation picks SEAL-RW-v1 instead, per Section 3.2.1 and [I-D.sullivan-cfrg-raae]. SEAL-AO-v1 provides the per-segment guarantees of SEAL-RO-v1, with one difference. Its re-mark leaves every formerly-terminal segment with an earlier ciphertext, the is_final = 1 form it held while it was terminal, which a write-once object never has. Presenting that earlier form in place of the current one is a same-index version substitution, which SnapVerify rejects. Under a null snapshot no SnapVerify runs, so an adversary can present the object at any length it legitimately held. That is rollback to an earlier honest state, which raAE places outside its scope at every snap_id ([I-D.sullivan-cfrg-raae]) and against which the consuming protocol supplies freshness. It does mean the finality check alone no longer detects truncation under this profile, which [I-D.sullivan-cfrg-raae] states. A protocol that needs the current segment set bound picks any snap_id but a null snapshot. SEAL-RO-v1 is the immutable profile (read-only). Because a rewrite would repeat the segment's derived nonce ([I-D.sullivan-cfrg-raae]), an encryptor under SEAL-RO-v1 MUST NOT rewrite a segment once it has been written. SEAL-RO-v1 provides per-segment confidentiality and integrity, binding the segment index and the finality bit, and key commitment through the commitment field. Under a null snapshot it has no snapshot or whole-object integrity: that, when needed, comes from any snap_id but a null snapshot, or from a layer above SEAL. Truncation detection under a null snapshot rests on the finality bit alone and surfaces only when the highest-indexed present segment verifies under is_final = 1 ([I-D.sullivan-cfrg-raae]), so a consumer of streamed plaintext has no completeness guarantee before that terminal check. +=============+===================+==============+=================+ | protocol_id | nonce_mode | snap_id | mutability | +=============+===================+==============+=================+ | SEAL-RW-v1 | random or derived | any but null | rewrite/extend/ | | | | | truncate | +-------------+-------------------+--------------+-----------------+ | SEAL-AO-v1 | derived | any | append-only | +-------------+-------------------+--------------+-----------------+ | SEAL-RO-v1 | derived | any | write-once | +-------------+-------------------+--------------+-----------------+ Table 5: SEAL profiles Sullivan Expires 6 May 2027 [Page 10] Internet-Draft SEAL November 2026 The snap_id column is a rule rather than a list, so an authenticator registered after this document is admitted without revising the table. Only SEAL-RW-v1 constrains the choice. It forbids the null snapshot, because a mutable object needs a snapshot to reject a superseded segment presented at its own index, and a profile that produces no keyed snapshot has nothing to reject it with. An encryptor MUST set payload_info to a (nonce_mode, snap_id, segment_commitment) tuple that is valid for its protocol_id, and a decryptor MUST reject any object whose tuple is not. A linear authenticator with the ciphertext digest set is one such invalid tuple (Section 6). A derived nonce under SEAL-RW-v1 requires an MRAE AEAD. SEAL-AO-v1 and SEAL-RO-v1 admit any AEAD because their disciplines keep each derived nonce unique ([I-D.sullivan-cfrg-raae]). 3.2.1. Choosing a Profile Pick the profile from how the stored content changes after it is first written. SEAL-RW-v1 (mutable) fits content updated in place: editable files, mutable object stores, and append-or-truncate logs. Its snapshot detects tampering with the current segment set as a whole, and the authenticators differ in update cost and per-segment-verify locality (Appendix C). SEAL-AO-v1 (append-only) fits content finished in pieces and never revised: growing archives, chunk stores, and logs that are never trimmed. Unlike SEAL-RO-v1 it admits the append of [I-D.sullivan-cfrg-raae]. The snapshot authenticator is the remaining choice. For a growing object the masked multiset hash (0x0001) suits best: it updates in time independent of n_seg wherever the tag it removes needs no check or comes from trusted state (Section 5.1), and its keyed snapshot does not grow. The epoch digest tree trades that for per-segment verify locality at a higher per-append cost. The digest transcript recomputes over the full leaf list, so appending an object costs O(n^2) in total (Appendix C). SEAL-log (Section 7) names this profile. It forgoes even the masked multiset hash for a null snapshot, taking per-segment integrity alone and leaving freshness to the consuming protocol, and pairs it with the append-log layout, which represents the short interior segment a re-mark leaves (Section 4.4). No named instantiation pairs this profile with a non-null snapshot. Sullivan Expires 6 May 2027 [Page 11] Internet-Draft SEAL November 2026 SEAL-RO-v1 (immutable) fits write-once content: archives, backups, content-addressed blobs, and write-once media. It is the smaller and simpler choice when content is never rewritten. [I-D.sullivan-cfrg-raae] covers epoch_length selection, and Section 3.1 covers the per-AEAD trade-offs that further narrow the suite. 4. Serialization Layouts Four serialization layouts, linear, aligned, split, and append-log, let a consuming protocol store raAE output. The parameterized SEAL construction mandates none of them. Each named instantiation binds one, and the consuming protocol pins the remaining details (Section 7). The commitment field in every layout is commitment_length octets ([I-D.sullivan-cfrg-raae]). The figures annotate it with its default, commitment_length = Nh. A layout's snapshot field holds the keyed snapshot. No layout stores the public snapshot. A verifier without the key recomputes it from the header, the metadata entries, which hold each leaf's leading fields, and each segment's A_i, without reading any ciphertext. The ciphertext digest is set whenever a public snapshot is used ([I-D.sullivan-cfrg-raae]), so for each segment it reads, the verifier recomputes LH(ct_i) from the bytes and compares it with the digest in that segment's metadata entry (Section 5.2). Under the epoch digest tree, the aux region holds only the keyed heads, so a keyless one-segment check takes the public heads as a witness (Section 5.3.1). In the aligned and split layouts, and in a linear layout read as a stream, each interior position has a fixed length: segment_max, or the leading slot's length for the aligned layout's unpadded first segment (Section 4.1, Section 4.2, Section 4.3). That length is the minimum segment size of [I-D.sullivan-cfrg-raae] for interior segments. A rewrite or length change under one of these layouts MUST leave no interior segment shorter than that length, or a reader cannot locate the segment. Under a mutable profile, a writer appending after a short terminal segment first rewrites it with its existing plaintext followed by the leading octets of the new content, filling it to that length, and then appends the rest. If the new content fits in the spare capacity, the rewrite alone is enough. In the aligned layout that rewrite can land in the unpadded leading slot, because an object with one segment keeps its terminal segment there. The slot's length depends on n_seg through the header size Sullivan Expires 6 May 2027 [Page 12] Internet-Draft SEAL November 2026 and is not monotone: n_slot steps up at each segment_max boundary of ct_start (Section 4.2), so a rewrite made before the append may not fit the slot the append leaves. An aligned object that will be appended to therefore takes the padded option (Section 4.2). So does one that will be truncated to a different count, because the slot's length changes with n_seg. An append-only profile cannot rewrite, so it can append after a short terminal segment only in a layout that stores each ciphertext length, such as the append-log layout (Section 4.4). 4.1. Linear Layout In a linear layout the salt, commitment, keyed snapshot, aux region (Section 4.2), and segment data appear in sequence. The salt comes first because it is needed to derive all payload schedule values. The commitment follows so a reader can reject a wrong key before reading any segment data. The keyed snapshot (Na octets), the configured authenticator's output, and the aux region precede the segment data so a streaming reader has both before the segments and can check the snapshot once all are read. Segments then follow in index order. When the object has at least one segment, that final segment carries is_final = 1. An empty object (n_seg = 0) has no final segment. +------+------------+----------+-------+-----------+-----+-----------+ | salt | commitment | snapshot | [aux] | segment | ... | segment | | (32) | (Nh) | (Na) | (aux) | 0 | | n_seg-1 | | | | | | ct + meta | | ct + meta | +------+------------+----------+-------+-----------+-----+-----------+ Figure 1: Linear Layout The aux region is present when the authenticator keeps whole-object state. Because the header here precedes the segments, a streaming write can reserve the region only when its size is known before the segments are written: a fixed-size aux is reserved and filled on the seek-back that also writes the keyed snapshot, while a count- dependent aux cannot be, so a streaming linear layout carries only a fixed-size one. A non-streaming linear write, which knows n_seg in advance, has no such limit. Each segment stores its ciphertext followed by its metadata entry, meta, the same meta_len-octet entry the aligned and split layouts hold in a separate region (Figure 4, Section 4.2). Sullivan Expires 6 May 2027 [Page 13] Internet-Draft SEAL November 2026 A streaming reader recovers segment boundaries from the segment lengths. Because a segment MAY be shorter than segment_max ([I-D.sullivan-cfrg-raae]), a linear layout that is to be read as a stream MUST keep every non-final segment at the full segment_max, leaving only the final segment short. A reader then finds each boundary at the fixed segment length. A layout that stores shorter interior segments MUST record their lengths so the reader can locate each segment. Linear layout supports streaming writes when the aux region is fixed size or empty. A writer emits the salt, the commitment, and a placeholder for the keyed snapshot and the aux region, then streams segments. After all segments are written the writer seeks back and writes the keyed snapshot and the aux region in place. A count- dependent aux has no placeholder to seek back to, so an authenticator with one is written in a non-streaming linear layout or in the aligned or split layout instead. 4.2. Aligned Layout In an aligned layout the ciphertext segments occupy slots aligned to segment_max, so a reader can seek to any segment with page-aligned I/ O. An arbitrary-length prefix, which the consuming protocol uses for its own machinery and which raAE does not specify, comes first. The raAE header follows, and the ciphertext follows the header. +--------------------------------------------+ | prefix (consuming protocol machinery, | | arbitrary length; not specified here) | +--------------------------------------------+ | header: salt, commitment, snapshot, | | per-segment metadata (broken out below) | +--------------------------------------------+ | leading slot: either the first ciphertext | | segment (< segment_max), or zero padding | | to a multiple of segment_max | +============================================+ | ciphertext segment (= segment_max) | +--------------------------------------------+ | ... | +--------------------------------------------+ | final ciphertext segment (<= segment_max) | +--------------------------------------------+ Figure 2: Aligned Layout Sullivan Expires 6 May 2027 [Page 14] Internet-Draft SEAL November 2026 Offsets are measured from index 0, the start of the prefix. Let ct_start be the offset at which the ciphertext begins (the prefix length plus the header length). The leading slot, from ct_start up to the first segment boundary n_slot * segment_max, is filled in one of two ways, chosen before writing: * No padding: the first ciphertext segment occupies the slot, with n_slot = ceil(ct_start / segment_max). It is shorter than segment_max and ends on the boundary n_slot * segment_max. * Padding: the slot is zero-padded to a multiple of segment_max, with n_slot >= ceil(ct_start / segment_max) (the next boundary, or a larger multiple to reserve whole segment slots for append headroom). From n_slot * segment_max onward every ciphertext segment begins at a multiple of segment_max and is a full segment_max octets, with the final segment at most segment_max. ct_start, n_slot, and the first- segment length all follow from the prefix and header sizes, so a writer computes them before emitting any ciphertext. The header holds the salt, the commitment, the keyed snapshot, the authenticator's aux region, and one metadata entry per segment: +------+------------+----------+---------+----------------------+ | salt | commitment | snapshot | [aux] | per-segment metadata | | (32) | (Nh) | (Na) | (aux) | (n_seg * meta_len) | +------+------------+----------+---------+----------------------+ Figure 3: Aligned Layout Header The bracketed aux region is present when the authenticator keeps whole-object state, and absent when it does not. The aligned header is sized from n_seg before it is written, so it carries a count- dependent aux without difficulty. Each metadata entry holds the optional ciphertext digest, the AEAD tag, and the optional stored nonce, in that order: +------------+-----------+------------+ | [LH(ct_i)] | tag_i | [nonce_i] | | (Nh) | (Nt = 16) | (Nn) | +------------+-----------+------------+ Figure 4: Metadata Entry Sullivan Expires 6 May 2027 [Page 15] Internet-Draft SEAL November 2026 The bracketed ciphertext digest LH(ct_i) is present only when the ciphertext digest is set, and the bracketed nonce_i only in random nonce mode. The tag is always present, Nt = 16 octets in every SEAL suite (Table 1). The fields are stored in leaf order: the metadata entry is the leading part of the strict authenticators' leaf, which appends the derived-mode counter and the segment AAD ([I-D.sullivan-cfrg-raae]), so an implementation forms each leaf from the stored bytes, the index, the finality bit, and A_i. Each metadata entry holds the optional Nh-octet digest, the Nt-octet tag, and the Np-octet presented nonce, so meta_len = Nt + Np octets, plus Nh when the ciphertext digest is set. A random-nonce entry sets Np = Nn and is Nt + Nn octets without the digest. A derived-nonce entry recomputes the nonce from the key schedule, so Np = 0 and the entry is Nt octets without the digest. The per-suite values, for a metadata entry with no ciphertext digest, are: +===========================+============+==========+ | AEAD | nonce mode | meta_len | +===========================+============+==========+ | AES-128-GCM, AES-256-GCM, | random (Nn | 28 | | ChaCha20-Poly1305 | = 12) | | +---------------------------+------------+----------+ | AEGIS-256, AEGIS-256X2 | random (Nn | 48 | | | = 32) | | +---------------------------+------------+----------+ | AES-256-GCM-SIV | derived | 16 | +---------------------------+------------+----------+ Table 6: Metadata entry size by AEAD The n_seg entries total n_seg * meta_len octets, so the whole header size is: header_size = 32 + commitment_length + Na + aux + n_seg * meta_len The aux term is defined in Section 2. Per-segment state does not count toward it: the stored part of a leaf sits in the metadata entry and is already carried by meta_len. A layout reserves the aux region whenever it is non-empty, so header_size and every offset derived from it follow from the wire parameters. Reserving the region is separate from populating it from storage: Section 5.3 lets an implementation recompute its heads from the leaves rather than keep them, which changes what a reader reads, not what the layout holds. Under the epoch digest tree the heads in this region are part of the witness that a subset SnapVerify reads ([I-D.sullivan-cfrg-raae]). Sullivan Expires 6 May 2027 [Page 16] Internet-Draft SEAL November 2026 With the default commitment_length = Nh, this specializes by authenticator. meta_len is not uniform across them: it grows by Nh when the ciphertext digest is set, which adds LH(ct_i) to each metadata entry (Figure 4). masked multiset hash (Na = Nh, aux = Nh, the masked accumulator): 32 + 3 * Nh + n_seg * meta_len digest transcript (Na = Nh, aux = 0): 32 + 2 * Nh + n_seg * meta_len epoch digest tree (Na = Nh, aux = n_ep * Nh): 32 + 2 * Nh + n_ep * Nh + n_seg * meta_len A reader verifies the commitment and the keyed snapshot from the header alone, then seeks to any segment using these offsets. The metadata entries hold the authenticator's per-segment inputs: tag_i for the masked multiset hash, or the leading fields of the strict authenticators' leaf, which a reader completes with each segment's index, finality bit, and A_i. A reader authenticates the whole object's snapshot from the header without reading or streaming any ciphertext. Under the digest transcript it MUST then check each leaf against the segment it reads ([I-D.sullivan-cfrg-raae]). Under the epoch digest tree a reader fetches the keyed snapshot and the epoch-heads region together, then reads one epoch's leaf run (Section 5.3) to check a segment. 4.3. Split Layout A split layout separates the ciphertext from the metadata into two streams that grow independently. The data stream holds the ciphertext segments, each a full segment_max except the last. Segment i is at offset i * segment_max. The metadata stream holds the salt, the commitment, any aux region, the n_seg per-segment metadata entries (each meta_len octets, broken out in Figure 4), and the keyed snapshot last. The metadata stream holds the authenticator's per-segment inputs ([I-D.sullivan-cfrg-raae]): tag_i for the masked multiset hash, or the leading fields of the strict authenticators' leaf, which a reader completes with each segment's index, finality bit, and A_i. A reader therefore authenticates the snapshot by reading only the metadata stream, never the data stream. When the authenticator keeps whole-object state, its aux region follows the commitment: the masked accumulator (Nh octets) or the epoch heads (n_ep * Nh octets). An entry's offset in the metadata stream therefore includes the aux length, which the authenticator and n_seg fix. Section 4.2 gives the region's byte layout. Sullivan Expires 6 May 2027 [Page 17] Internet-Draft SEAL November 2026 data stream: +------------------------------+ | seg 0 ct (= segment_max) | +------------------------------+ | seg 1 ct (= segment_max) | +------------------------------+ | ... | +------------------------------+ | seg n_seg-1 (<= segment_max) | +------------------------------+ metadata stream: +------+------------+-------+------------+-----+------------+----------+ | salt | commitment | [aux] | meta_0 | ... | meta_last | snapshot | | (32) | (Nh) | (aux) | (meta_len) | | (meta_len) | (Na) | +------+------------+-------+------------+-----+------------+----------+ Figure 5: Split Layout Because neither stream embeds the other, both grow by appending. Extending a nonempty message replaces the old terminal ciphertext and its metadata entry, then appends k ciphertext segments to the data stream and k metadata entries to the metadata stream, with k >= 1, and updates the keyed snapshot and any aux region. An extension from the empty object only adds the k records and updates the snapshot and aux. Truncating drops the tail of each stream, re-marks the new last segment and rewrites its metadata entry unless it empties the object, and rewrites the keyed snapshot and any aux region. Neither operation shifts an existing ciphertext segment, which the in-place aligned layout cannot avoid once the header grows. 4.4. Append-Log Layout An append-log layout serves an append-only object read or extended sequentially. It holds the salt, the commitment, then each segment preceded by the two-octet length of its ciphertext. Nothing follows the segments: the append-log layout holds no keyed snapshot or aux region, so it supports only a null snapshot, which SEAL-log selects (Section 7). +------+------------+-------+-----------+-----+-------+-----------+ | salt | commitment | len | segment | ... | len | segment | | (32) | (Nh) | (2) | 0 | | (2) | n_seg-1 | | | | | ct + meta | | | ct + meta | +------+------------+-------+-----------+-----+-------+-----------+ Sullivan Expires 6 May 2027 [Page 18] Internet-Draft SEAL November 2026 Figure 6: Append-Log Layout The length before segment i is uint16(len_i), the octet length of ct_i. The field limits the layout to a segment_max of at most 65535 octets, so of the sizes this document defines only 16384 fits it. The length covers ct_i only: the meta_len-octet metadata entry (Figure 4) follows the ciphertext and, being fixed-width, needs no length of its own. Storing the length lets an interior segment be shorter than segment_max. An append writes the new segments at the end and re-marks the previous terminal segment in place, disturbing neither the header nor any earlier segment. Sequential append and streaming read are native. Random access needs a walk from the start or an external index, which suits a log. 4.5. Reduced Layouts under a Derived Nonce Under a derived-nonce profile (SEAL-RO-v1 or SEAL-AO-v1), stored fields drop out of the layouts above. Each nonce is recomputed from the key schedule, so Np = 0 and no nonce is stored. SEAL-AO-v1 additionally lets an append-only object carry a short interior segment, held by the append-log layout (Section 4.4). Under a null snapshot the keyed snapshot drops (Na = 0) and a metadata entry is the Nt-octet tag alone. The linear layout reduces to the salt, the commitment, and the per-segment ciphertext and tags. The aligned and split header sizes are: snap_id 0x0000 (Na = 0, aux = 0): 32 + commitment_length + Nt * n_seg snap_id 0x0001 (Na = Nh, aux = Nh, no stored leaf): 32 + commitment_length + 2 * Nh + Nt * n_seg snap_id 0x0002 (Na = Nh, aux = 0, leaf fields in meta_len): 32 + commitment_length + Nh + (Nh + Nt) * n_seg snap_id 0x0003 (Na = Nh, aux = n_ep * Nh stored): 32 + commitment_length + Nh + n_ep * Nh + (Nh + Nt) * n_seg The two strict-authenticator lines take the ciphertext digest ([I-D.sullivan-cfrg-raae]) as set, so each entry carries LH(ct_i) beside the tag. Without it the entry is the Nt-octet tag, as under snap_id 0x0000. An authenticator that keeps whole-object state also carries its aux region in the header (Section 4.2). In the aligned layout every ciphertext segment begins at a multiple of segment_max, so a reader seeks to any segment by arithmetic on its index and verifies it from its own tag. Sullivan Expires 6 May 2027 [Page 19] Internet-Draft SEAL November 2026 4.6. Content-Defined Chunking (Reserved) Segments take writer-chosen boundaries, each carrying up to segment_max octets. Two modes that place those boundaries by content, using content-defined chunking, are reserved for a future revision. They are an append-only mode and an in-place-editing mode with expansion. Neither is specified here. 5. Snapshot Authenticator Instantiations These snapshot authenticators instantiate the interface of [I-D.sullivan-cfrg-raae], each selected by a snap_id value (Section 3.1). A null snapshot (snap_id 0x0000) configures no authenticator and is defined in [I-D.sullivan-cfrg-raae]. 5.1. Masked Multiset Hash This snapshot authenticator builds an updatable, masked multiset hash over the segment tags. It accumulates one indexed, keyed contribution per segment, in the MSet-XOR-Hash form of Clarke et al. ([MSetHash]), then publishes that accumulator behind a deterministic mask this document adds. Its per-segment contribution is: contrib(i) = KDF(protocol_id, contrib_label, [snap_key], [uint64(i), tag_i], Nh) The masked multiset hash fixes three labels: contrib_label = "acc_contrib", snapshot_label = "snap_acc", and acc_mask_label = "acc_mask", each distinct from all other SEAL labels. The finality bit is bound through tag_i: it is an AEAD input in both nonce modes (segment_aad in random mode, the low nonce bit in derived mode, [I-D.sullivan-cfrg-raae]), so flipping it changes tag_i, hence contrib(i), hence the accumulator. No explicit binding in contrib is needed. The masked multiset hash aggregates the per-segment contributions into the accumulator acc by bitwise XOR: acc = contrib(0) XOR contrib(1) XOR ... XOR contrib(n_seg-1) The accumulator is an Nh-octet value, with the all-zero string as the value for the empty segment set. XOR is order-independent, so the accumulator does not depend on segment order, and it is its own inverse, so rewriting segment i removes its old contribution and adds the new one by XOR, in O(1) time, and without re-reading any other segment where the old tag needs no check or comes from trusted state. Sullivan Expires 6 May 2027 [Page 20] Internet-Draft SEAL November 2026 The accumulator is linear, and publishing it in the clear would let a write adversary recombine the differences between successive values into a non-historical segment set (Appendix B). The masked multiset hash therefore does two things. First it authenticates the count and accumulator as the snapshot, a message authentication code (MAC) under snap_key with its own label: snapshot(n_seg, acc) = KDF(protocol_id, snapshot_label, [snap_key], [uint64(n_seg), acc], Nh) Write snapshot = snapshot(n_seg, acc) for the present count and accumulator. Second, the masked multiset hash derives a mask from snap_key under the third label, seeded by that snapshot rather than by a fresh nonce, and XORs it into the accumulator to form the masked accumulator wrapped_acc: acc_mask(n_seg, snapshot) = KDF(protocol_id, acc_mask_label, [snap_key], [uint64(n_seg), snapshot], Nh) mask = acc_mask(n_seg, snapshot) wrapped_acc = acc XOR mask Seeding the mask with the snapshot rather than a fresh nonce is the synthetic-IV derandomization of deterministic authenticated encryption ([DAE]): the snapshot stays a deterministic function of (n_seg, acc) while the stored accumulator is hidden behind a one-time pad (Appendix B). The count n_seg is not stored in the keyed snapshot. The verifier supplies it as the number of segments present, and the snapshot binds it under the MAC. An object with n_seg = 0 is a valid empty object. It holds the salt, the commitment, and the keyed snapshot, with no ciphertext segments and no per-segment tags. Its accumulator is the XOR over the empty segment set, namely 0^Nh, and the same masking as any other count applies, so the empty snapshot, meaning this authenticator's snapshot over zero segments rather than an absent one, is not a fixed constant but a masked authenticator over that empty set: acc = 0^Nh snapshot = snapshot(0, 0^Nh) mask = acc_mask(0, snapshot) wrapped_acc = 0^Nh XOR mask = mask An empty object is distinct from a zero-length plaintext, which is one final segment (n_seg = 1) whose plaintext has length 0. Sullivan Expires 6 May 2027 [Page 21] Internet-Draft SEAL November 2026 Where the old tag needs no check or comes from trusted state, the writer can rewrite segment i without re-reading the other tags. The writer first recovers the accumulator from the stored wrapped_acc by removing the mask, acc = wrapped_acc XOR acc_mask(n_seg, snapshot). It then XORs contrib(i) computed from the old tag (stored alongside the ciphertext) out of acc, XORs in contrib(i) computed from the new tag, and produces the new snapshot over the unchanged count. Under the writer rule of [I-D.sullivan-cfrg-raae], a writer that does not take wrapped_acc from locally trusted snapshot state recovers acc by checking it against a verified snapshot, with n_seg the count that snapshot is for: recover_acc(wrapped_acc, n_seg, verified_snapshot): acc = wrapped_acc XOR acc_mask(n_seg, verified_snapshot) snapshot_calc = snapshot(n_seg, acc) compare snapshot_calc to verified_snapshot in constant time (on a mismatch, return failure and do not use wrapped_acc) return acc A party without snap_key that alters wrapped_acc, or the count, changes an input to snapshot(n_seg, acc), so the check passes only if that function agrees on two distinct inputs, which under the PRF assumption on snap_key that the key schedule already carries happens with probability about 2^-(8*Nh) per attempt. On a mismatch the writer can instead recompute acc from every stored tag, with verify's index-set check. It uses that acc only if snapshot(n_seg, acc) matches the verified snapshot under the same constant-time comparison, and otherwise the update fails. The update's witness ([I-D.sullivan-cfrg-raae]) is therefore wrapped_acc and, where a removed tag is checked by recomputation, every stored tag. As with verify, the writer MUST NOT surface any unmasked accumulator it holds, whether recovered, recomputed, or updated, or any contribution value, and the only snapshot state it publishes is the new snapshot and wrapped_acc (Appendix B). The tag that an update XORs out, for a segment it rewrites, re-marks, or drops, needs no check under the writer rule of [I-D.sullivan-cfrg-raae] in two cases only: in random nonce mode while the nonce source does not repeat, and in a SEAL-AO-v1 re-mark. In both, where the update decrypts the segment, the tag removed is that record's own, read once with it. If the tag removed is not the one whose contribution acc holds, acc keeps the XOR of the two contributions, and the in-place update carries that leftover into every later update. In a SEAL-AO-v1 re-mark the record decrypts under the derived nonce of (t, 1), and [I-D.sullivan-cfrg-raae] permits one encryption at that pair under the salt. So a record that decrypts is the one the writer wrote, and its tag is the one acc holds. A record that does not decrypt fails the update. Sullivan Expires 6 May 2027 [Page 22] Internet-Draft SEAL November 2026 In random nonce mode, while the nonce source does not repeat, the argument is a count over the terms at one index. A stale removal leaves two terms unpaired, the tag acc still holds and the stale tag it removed. Those two, and every fresh tag added there from the stale update on, are cancelled only by a removed tag the adversary chooses afterwards, or by the one tag a verifying set presents. A rewrite or re-mark brings one removed tag and one fresh tag, a drop a removed tag alone, and a re-add a fresh tag alone. An index is dropped only while the object holds it and re-added only while it does not. So the tags removed after the stale one, together with any tag that a verifying set presents there, always number two fewer than the terms left to cancel. The index can be made unverifiable, but never laundered. Every other removal takes its tag from locally trusted snapshot state or checks it by the recompute above: any removal under SEAL-RW-v1 with a derived nonce, and any removal once the random nonce source can repeat. Duplicated state of a cryptographically secure pseudorandom number generator (CSPRNG) can make it repeat: directly drawn nonces repeat, and a plaintext-bound nonce repeats when the plaintext, index, and segment AAD repeat too. Under HedgedRandom a nonce repeats on duplicated state only when tag2 repeats with it, which the companion forbids, or by an output collision ([I-D.sullivan-cfrg-raae]). Where such an update decrypts the segment, as a re-mark does, it compares that record's tag with the checked one. Under a derived nonce, an identical rewrite reproduces the current tag, so XORing out an older tag in its place leaves exactly the accumulator of the set that holds the older version. A rewrite back to the older plaintext reproduces the removed tag instead and leaves acc unchanged, so the new snapshot keeps certifying the version the writer replaced. The snapshot is Nh octets, so Na = Nh, as it is for every other authenticator here that produces one ([I-D.sullivan-cfrg-raae] defines Na in general). wrapped_acc is not part of it. SnapVerify never reads wrapped_acc: it recomputes the accumulator from the tags present and recomputes the snapshot over the count it observes. wrapped_acc exists so that a writer can rewrite one segment without re-reading every other tag where the old tag needs no check or comes from trusted state. That makes it a summary in the sense of Section 11, derivable from the per-segment inputs and stored only to bound the read set. It is this authenticator's aux region, Nh octets (Section 4.2). The epoch digest tree's heads are another authenticator's aux region, and an object carries the aux region of its own snap_id alone. Sullivan Expires 6 May 2027 [Page 23] Internet-Draft SEAL November 2026 SnapVerify runs only under a state derived from the CEK, payload_info, and G: the writer's own, or a reader's after the commitment check of decryption ([I-D.sullivan-cfrg-raae]). The snapshot's per-object binding rests on snap_key (Appendix B). The masked multiset hash realizes verify(snapshot) for the SnapVerify of [I-D.sullivan-cfrg-raae]. SnapVerify supplies the count n_seg and the present segment tags: verify(snapshot): ;; Caller (SnapVerify) MUST already have checked that the present ;; indices are exactly 0..n_seg-1, each once. acc_calc = XOR over i of contrib(i) ;; present segment tags snapshot_calc = snapshot(n_seg, acc_calc) ;; the MAC over the count compare snapshot_calc to snapshot in constant time (on any mismatch, reject) return accept The comparison runs in constant time ([I-D.sullivan-cfrg-raae]). Because the accumulator is order-independent under XOR, a duplicate or missing index can leave acc_calc recomputing to the genuine accumulator and the comparison passing, which is why SnapVerify's index-set check ([I-D.sullivan-cfrg-raae]) is mandatory. verify returns only accept or reject. An implementation MUST NOT surface the recomputed accumulator, any contribution value, or any value derived from them, and MUST NOT signal through an error code or timing anything beyond that one bit. The recomputed accumulator is what the at-rest mask hides from a write adversary (Appendix B): exposing it here would reinstate the recombination attack. A modified tag or count changes the recomputed snapshot, and a different key or parameter context fails the commitment check that precedes a reader's SnapVerify ([I-D.sullivan-cfrg-raae]). The forgery bound is in Section 6.1 and the rollback limitation in [I-D.sullivan-cfrg-raae]. 5.2. Digest Transcript This snapshot authenticator is a per-object digest transcript: one keyed KDF evaluation over the digest of the ordered run of per- segment leaves (Section 2). It has no accumulator and no mask, and it recomputes over the full leaf list on any rewrite. Its ordering is strict: a fixed, published snapshot cannot be made to verify a different ordered leaf list, even by a party that holds the CEK and every key derived from it. Sullivan Expires 6 May 2027 [Page 24] Internet-Draft SEAL November 2026 When the ciphertext digest is set, so the leaf carries LH(ct_i), the snapshot additionally binds each segment's ciphertext bytes, along with its AEAD tag, nonce field, and segment AAD (Section 6.2). A consuming protocol that authenticates the keyed snapshot, for example by binding it into a signed reference or manifest, extends that binding to per-segment ciphertext origin authentication. Its per-segment input is the segment leaf leaf(i) of [I-D.sullivan-cfrg-raae], with ct_i and tag_i following the AEAD- output convention of Section 2. In derived nonce mode the leaf's nonce field is an 8-octet counter. LH is the over-large-field digest of [I-D.sullivan-cfrg-raae], so where the ciphertext digest is set LH(ct_i) is a fixed Nh-octet value that binds segment i's ciphertext through the collision resistance of LH for any segment length. The leaf's length varies with A_i, and the run that carries the leaves frames each one. Without the ciphertext digest, the transcript binds the tags, nonce fields, and segment AAD but not the ciphertext content. The ciphertext digest is a deterministic function of public object bytes, so any party can recompute or publish it without holding a key. The tag is the value the AEAD already produced for the segment. leaf(i) is the digest transcript's per-segment input, taking the role the tag plays for the masked multiset hash ([I-D.sullivan-cfrg-raae]). In random nonce mode the leaf carries the presented nonce, so different nonces give different leaves even against a key holder. In both nonce modes the leaf carries the finality bit and the segment AAD, so a verifier needs each segment's A_i to form it. Position within the object is bound by the ordered run below and the count by n_seg. The digest transcript places no restriction on the nonce mode. Every value of the digest transcript and of the epoch digest tree (Section 5.3), keyed or public, has one shape: a KDF over the payload_info elements, the commitment, a count, and the LH digest of a run, with ikm = [snap_key] for a keyed value and ikm = [] for a public one. KDF(protocol_id, label, ikm, [...payload_info, commitment, uint64(count), LH(run)], Nh) The digest transcript's keyed snapshot, written snapshot, and its public snapshot ([I-D.sullivan-cfrg-raae]), written pub_snapshot, take the count n_seg and the run of every leaf in index order: Sullivan Expires 6 May 2027 [Page 25] Internet-Draft SEAL November 2026 transcript_run = encode(leaf(0), ..., leaf(n_seg-1)) +============+====================+==========+=====+==============+ |Value |label |ikm |count|run | +============+====================+==========+=====+==============+ |snapshot |transcript_label |[snap_key]|n_seg|transcript_run| +------------+--------------------+----------+-----+--------------+ |pub_snapshot|pub_transcript_label|[] |n_seg|transcript_run| +------------+--------------------+----------+-----+--------------+ Table 7: Digest Transcript Values Each value is Nh octets, so Na = Nh. The two labels, transcript_label = "snap_transcript" and pub_transcript_label = "snap_transcript_pub", are distinct from each other and from all other SEAL labels under the encode frame, and ...payload_info passes each payload_info element as a separate info element ([I-D.sullivan-cfrg-raae]). The run enters as its LH digest, so a reader can absorb the leaves into an incremental LH state as segments arrive and derive the value from the digest and the count without holding the run. The ordering is strict, so the digest transcript has intermediate snapshots ([I-D.sullivan-cfrg-raae]): at a count k at which the object is open, its values are the formulas above with k in place of n_seg, over leaf(0) through leaf(k-1), each formed with is_final = 0, and a reader absorbing leaves in order derives the value at any k from a copy of its LH state. The commitment element repeats the object's commitment ([I-D.sullivan-cfrg-raae]) inside the value. For SnapVerify it binds G, which snap_key does not: a snapshot from an object that shares this object's CEK and payload_info but not its G fails against this object's commitment. It is also load-bearing for a consuming protocol that lifts either value into a signature or MAC, which then carries the full object context (CEK, payload_info, and G) and cannot be replayed against an object with a different context (Section 6.2). The payload_info elements add nothing to the keyed snapshot that the commitment element and snap_key do not already bind. For the public snapshot they are what binds every parameter that governs parsing and algorithm choice, since a verifier without the key cannot check payload_info through the commitment. The digest transcript realizes verify(snapshot) as follows. The caller (SnapVerify, [I-D.sullivan-cfrg-raae]) MUST already have checked that the present indices are exactly 0..n_seg-1, each once. Sullivan Expires 6 May 2027 [Page 26] Internet-Draft SEAL November 2026 verify(snapshot): ;; The caller has checked the present index set (see above). for i in 0 .. n_seg-1: leaf_calc(i) = [LH(ct_i)] || tag_i || nonce_field(i) || segment_aad(i, is_final, A_i) over the stored segment i and its A_i snapshot_calc = KDF(protocol_id, transcript_label, [snap_key], [...payload_info, commitment, uint64(n_seg), LH(encode(leaf_calc(0), ..., leaf_calc(n_seg-1)))], Nh) compare snapshot_calc to snapshot in constant time (on any mismatch, reject) return accept The comparison runs in constant time ([I-D.sullivan-cfrg-raae]). The index-set check in SnapVerify remains mandatory ([I-D.sullivan-cfrg-raae]). Here it is defense in depth rather than load-bearing, because a malformed index multiset changes the framed leaf list and the recomputed value with it. The digest transcript is per-object ([I-D.sullivan-cfrg-raae]): it provides no add, remove, or set_length, and produces the snapshot in a single KDF call over the digest of the leaf run. Any change to the segment set recomputes the transcript over the new leaf list and re- publishes the keyed snapshot. A write-once profile computes it once, at encryption, and only verifies it thereafter. A change folds every leaf it keeps unchanged into the new snapshot, formed from the stored entries and each segment's A_i, read from a carried leaf list, or recomputed from the stored segments, so the writer rule of [I-D.sullivan-cfrg-raae] covers the list, which is the witness of both an update and a subset SnapVerify ([I-D.sullivan-cfrg-raae]), with length the count. Unless the writer produced the list itself as the sole writer, it first recomputes the transcript over it as it stands and compares the result with a verified snapshot in constant time, as verify does. On a mismatch the update fails. A segment the change decrypts, as a re-mark does, passes when the leaf recomputed from its record matches its entry in the checked list. An object with n_seg = 0 is a valid empty object under the digest transcript. The run is empty and the keyed snapshot is KDF(protocol_id, transcript_label, [snap_key], [...payload_info, commitment, uint64(0), LH("")], Nh), so the count is bound and truncation to the empty object is distinguishable from a legitimate empty object. Sullivan Expires 6 May 2027 [Page 27] Internet-Draft SEAL November 2026 verify above recomputes each leaf from the stored segment bytes and A_i, so whole-object SnapVerify reads the full object. A layout holds the leading fields of leaf(i) in each per-segment metadata entry, and a consuming protocol MAY carry the leaf list beside the object, for example in a manifest. A reader MAY then verify the transcript over the leaves formed from the stored entries and each segment's A_i, or over the carried leaves, without reading any ciphertext. It MUST compare leaf(i), recomputed from the one segment it reads and its A_i, against the leaf at position i before treating that segment as covered by the snapshot. A segment whose recomputed leaf does not match fails authentication. The procedure verifies any subset of the segments, down to a single one, against the whole- object snapshot, and is how a consuming protocol obtains per-segment ciphertext origin authentication from one authenticated keyed snapshot. No layout stores the public snapshot (Section 4). Once it is authenticated, so is every parameter payload_info carries, and a verifier that holds no key MUST parse the leaves under those parameters. Such a verifier checks an authenticated public snapshot as verify does, with the public label and an empty ikm, recomputing it over leaves formed from the stored segments or entries and each segment's A_i, or over carried leaves under the per-segment comparison above, and comparing in constant time, though the comparison carries no secret. A modified leaf or count changes the recomputed transcript, and a different key or parameter context fails the commitment check that precedes a reader's SnapVerify ([I-D.sullivan-cfrg-raae]). The forgery and binding arguments are in Section 6.2. 5.3. Epoch Digest Tree This snapshot authenticator is an epoch digest tree. Segment leaves (Section 5.2) fold into per-epoch heads, and the heads fold into the snapshot. A reader verifies one segment from two aligned metadata reads plus the segment itself: the target epoch's leaf run, and the epoch-heads region. Each of those two metadata reads is one segment_max or less for objects up to roughly 128 GiB at the recommended parameters (Table 9). The design has the digest transcript's binding properties and adds this bounded random-access verification for large objects. Sullivan Expires 6 May 2027 [Page 28] Internet-Draft SEAL November 2026 epoch 0 epoch n_ep-1 +---------------------------+ +---------------------------+ | ct_0 ct_1 ... ct_{2^r-1} | ... | ct_... ... ct_{n_seg-1} | +---------------------------+ +---------------------------+ | | keyed transcript over keyed transcript over the epoch's leaves the epoch's leaves | | d_0 d_{n_ep-1} +-------------------+-----+-----+--------+ | keyed transcript over epoch heads | snapshot Figure 7: Epoch digest tree: leaves in each epoch are folded by a keyed transcript into one epoch head, and the epoch heads are folded by a keyed transcript into the snapshot. There is no Merkle path Its per-segment input is the segment leaf of [I-D.sullivan-cfrg-raae], as for the digest transcript (Section 5.2). d_e and pd_e are the epoch heads over one epoch's leaves, and snapshot and pub_snapshot are the digests over d_0 through d_{n_ep-1} and over pd_0 through pd_{n_ep-1}. Segments are grouped into epochs of 2^r consecutive indices, where r = epoch_length ([I-D.sullivan-cfrg-raae]) is reused as the grouping fan-out, so no new parameter is added. Only the final epoch may be short. epoch e = segments e * 2^r .. min((e+1) * 2^r, n_seg) - 1 n_ep = ceil(n_seg / 2^r) epochs There are n_ep epochs, the same grouping the epoch key uses, so each epoch head covers exactly the segments under one epoch key. Each value of the epoch digest tree has the shape of Section 5.2. Epoch e's two heads, d_e for the keyed snapshot and pd_e for the public snapshot, take as count the number of leaves in the epoch, m_e, and as run the epoch's leaves. The two values over the heads, snapshot and pub_snapshot, take n_seg and the heads in order: epoch_run(e) = encode(leaf(e * 2^r), ..., leaf(last index in epoch e)) m_e = min(2^r, n_seg - e * 2^r) Sullivan Expires 6 May 2027 [Page 29] Internet-Draft SEAL November 2026 +============+======================+==========+=====+==============+ |Value | label |ikm |count|run | +============+======================+==========+=====+==============+ |d_e | epoch_head_label |[snap_key]|m_e |epoch_run(e) | +------------+----------------------+----------+-----+--------------+ |pd_e | pub_epoch_head_label |[] |m_e |epoch_run(e) | +------------+----------------------+----------+-----+--------------+ |snapshot | head_label |[snap_key]|n_seg|d_0 || ... || | | | | | |d_{n_ep-1} | +------------+----------------------+----------+-----+--------------+ |pub_snapshot| pub_head_label |[] |n_seg|pd_0 || | | | | | |... || | | | | | |pd_{n_ep-1} | +------------+----------------------+----------+-----+--------------+ Table 8: Epoch Digest Tree Values In an object with one epoch, d_0 is the digest transcript's snapshot formula over the same object under epoch_head_label in place of transcript_label. The leaves vary in length with A_i, so the epoch run frames each one as an element of encode ([I-D.sullivan-cfrg-raae]), and a leaf longer than 0xFFFE octets enters as its LH digest. The epoch heads are fixed length (Nh octets). Each run therefore parses unambiguously, with an element count that n_seg and epoch_length fix. A reader forms epoch_run(e) from the epoch's stored run, its metadata entries in leaf order, with each segment's index, finality bit, and A_i. Where this document gives the size of an epoch's leaf run or counts reads of it, it means the stored run. Each head carries payload_info, the commitment, and its leaf count, and each leaf in its run carries its own index, in the nonce field in derived nonce mode and in the segment AAD in random nonce mode. The heads of different objects or epochs are therefore computed over different inputs, and each head's place in the ordered heads run binds its position as well. A short final epoch is bound by its head's count and by n_seg and epoch_length in the root. The segmentation is recovered only from n_seg and epoch_length, so a profile that selects the epoch digest tree MUST carry epoch_length in payload_info, as every profile in this document does. The heads and the root then bind it for both values, and the keyed snapshot binds it through snap_key as well, so no party can present one heads run under a different fan-out. Sullivan Expires 6 May 2027 [Page 30] Internet-Draft SEAL November 2026 Each run enters the KDF as its LH digest, as in Section 5.2, so a reader carries an incremental LH state across segments and epoch boundaries. Every element the KDF sees is at most Nh octets, the 32-octet salt included. Each value is Nh octets, so Na = Nh. The epoch digest tree fixes four labels, epoch_head_label = "snap_epoch" and head_label = "snap_epoch_root" for the keyed snapshot, and pub_epoch_head_label = "snap_epoch_pub" and pub_head_label = "snap_epoch_root_pub" for the public snapshot, each distinct from all other SEAL labels under the encode frame ([I-D.sullivan-cfrg-raae]). The payload_info elements, the commitment, and the count are bound as in Section 5.2, and the snap_key ikm makes the keyed snapshot unforgeable without the key ([I-D.sullivan-cfrg-raae]). The leaf binds the tag, the nonce field, and the segment AAD (Section 5.2), so the epoch digest tree places no restriction on the nonce mode. The layout stores the n_ep epoch heads, in the aligned layout's header beside the keyed snapshot or after the commitment in the split layout's metadata stream (Section 4.3), and the leading fields of each leaf in its metadata entry. The keyed snapshot is Nh octets. The epoch heads are recomputable from the leaves, so an implementation MAY recompute them rather than rely on the stored ones, at the cost of reading every entry. The serialization layouts reserve and populate the region whenever snap_id is 0x0003 (Section 4.2), because header_size and the segment offsets derived from it must follow from the wire parameters alone. Recomputing is therefore a choice about what a reader trusts, not a licence to omit the octets. The two-read random-access verification below requires a layout whose metadata entries are contiguous within an epoch, so one seek reads the whole epoch. A layout that interleaves the entries with the ciphertext does not have this property, and a reader gathers an epoch's entries with one seek per segment. verify(snapshot) recomputes the whole transcript. The caller (SnapVerify, [I-D.sullivan-cfrg-raae]) MUST already have checked that the present indices are exactly 0..n_seg-1, each once. Sullivan Expires 6 May 2027 [Page 31] Internet-Draft SEAL November 2026 verify(snapshot): ;; The caller has checked the present index set (see above). for e in 0 .. n_ep-1: d_calc(e) = KDF(protocol_id, epoch_head_label, [snap_key], [...payload_info, commitment, uint64(m_e), LH(epoch_run(e))], Nh) snapshot_calc = KDF(protocol_id, head_label, [snap_key], [...payload_info, commitment, uint64(n_seg), LH(d_calc(0) || ... || d_calc(n_ep-1))], Nh) compare snapshot_calc to snapshot in constant time (on any mismatch, reject) return accept Random-access verification of one segment, in the aligned or split layout (Section 4.2, Section 4.3), costs two aligned reads beyond the segment itself. A reader verifies the head once and keeps the epoch heads, then per segment: verify_segment(i): ;; aligned or split layout ;; once per object, then cached: read the epoch-heads region (n_ep * Nh octets) from the header (aligned layout) or the metadata stream (split layout) head_calc = KDF(protocol_id, head_label, [snap_key], [...payload_info, commitment, uint64(n_seg), LH(d_0 || ... || d_{n_ep-1})], Nh) check head_calc equals the stored snapshot (constant time) ;; per segment i: e = i >> r read epoch e's metadata entries, m_e of them, from segment e * 2^r's metadata entry ;; one aligned read form epoch_run(e) from them with each segment's index, finality bit, and A_i d_calc = KDF(protocol_id, epoch_head_label, [snap_key], [...payload_info, commitment, uint64(m_e), LH(epoch_run(e))], Nh) check d_calc equals the cached d_e (constant time) read segment i from the data region leaf_calc = [LH(ct_i)] || tag_i || nonce_field(i) || segment_aad(i, is_final, A_i) check leaf_calc equals the leaf at position i mod 2^r within epoch e's run (constant time) A cold single-segment check is two metadata reads, the epoch-heads region and one epoch's leaf run, each one aligned read up to the ceiling (Table 9). A reader scanning many segments reads the epoch- heads region once and then one epoch leaf run per epoch, so the amortized cost is one metadata read per segment. Those two reads, Sullivan Expires 6 May 2027 [Page 32] Internet-Draft SEAL November 2026 with the count n_seg that head_calc binds, are the witness of a subset SnapVerify ([I-D.sullivan-cfrg-raae]). For several presented segments the witness is the count, the epoch heads, and the leaf run of each epoch that holds one, and an update's witness is the epoch heads and the affected epoch's leaf run. These costs hold when the epoch's A_i are empty or available to the reader without further reads. All comparisons run in constant time ([I-D.sullivan-cfrg-raae]). A segment whose recomputed leaf, epoch head, or head does not match fails authentication. The reader recomputes only its own leaf and its own epoch head and reads no other segment's ciphertext. The epoch digest tree is per-object ([I-D.sullivan-cfrg-raae]): it provides no add, remove, or set_length. A rewrite of segment i recomputes only epoch e's head and the keyed snapshot, where e = i >> epoch_length, leaving the other epoch heads unchanged. As with any keyed snapshot, a consuming protocol that signed the value re-signs the new one. Under a write-once profile each snapshot, the final one and any intermediate one, is computed once, at its count. Under an append-only or mutable profile a change is the same two aligned metadata reads as verification (the epoch's leaf run and the epoch-heads region) followed by three metadata writes: the new entry, the new epoch head, and the new keyed snapshot. A change folds into the new snapshot the heads of the epochs it leaves unchanged, the leaves it keeps unchanged in each epoch whose head it recomputes, and the plaintext of any segment it decrypts. Unless the writer produced them itself as the sole writer, it checks the heads and leaves with the first two comparisons of verify_segment, made against a verified snapshot ([I-D.sullivan-cfrg-raae]) rather than the stored one: head_calc over the stored heads, then d_calc over the run formed from each such epoch's stored entries against that epoch's checked head. On any mismatch the update fails. A segment the change decrypts passes when the leaf recomputed from its record matches its entry in the checked run, verify_segment's third comparison. An object with n_seg = 0 has n_ep = 0 and an empty heads run: snapshot = KDF(protocol_id, head_label, [snap_key], [...payload_info, commitment, uint64(0), LH("")], Nh) so the count is bound and truncation to the empty object is distinguishable. Sullivan Expires 6 May 2027 [Page 33] Internet-Draft SEAL November 2026 Choose the largest epoch_length whose epoch leaf run still fits one aligned read, so recomputing an epoch head is a single read: 2^r * meta_len <= segment_max The values below are for a derived-nonce entry with the ciphertext digest set (Np = 0, meta_len = Nh + Nt). A random-nonce entry has meta_len = Nh + Nt + Np, so its stored run grows by Np octets per segment. Recompute r from the rule above. +=============+==========+========================+ | segment_max | Nh | Suggested epoch_length | +=============+==========+========================+ | 65536 | 32 or 48 | 10 | +-------------+----------+------------------------+ | 65536 | 64 | 9 | +-------------+----------+------------------------+ | 16384 | 32 or 48 | 8 | +-------------+----------+------------------------+ | 16384 | 64 | 7 | +-------------+----------+------------------------+ Table 9: Suggested epoch_length by segment_max and KDF output length Nh The epoch-heads region of the header is n_ep * Nh octets, one aligned read up to about 128 GiB at r = 10, Nh = 32, and segment_max 65536. Past that it spans 1 + ceil(content size / that ceiling) reads. The forgery and binding arguments are in Section 6.3. The random-access benefit is realized only when epoch_length is in the grouping range of Table 9. A profile that sets a large epoch_length for key rotation, such as a flat key at 63, makes n_ep = 1, so the fold is a single epoch: still correct, with no random- access benefit. A single epoch is still the two-level epoch digest tree under its own labels, not the digest transcript value. A profile that wants random-access verification sets epoch_length to the Table 9 value and accounts for the resulting per-epoch-key write budget in [I-D.sullivan-cfrg-raae]. 5.3.1. Public and Intermediate Snapshots The epoch digest tree's public snapshot takes the public labels and an empty ikm at both levels, so its heads pd_e are separate values from the keyed heads d_e. A verifier that holds no key MUST parse leaves, runs, and witness heads under the parameters the authenticated value carries. Sullivan Expires 6 May 2027 [Page 34] Internet-Draft SEAL November 2026 No layout stores the public heads or the public snapshot (Section 4). A verifier that holds no key recomputes them from the leaves, or takes exactly n_ep * Nh octets of heads as a witness that the consuming protocol carries beside the authenticated pub_snapshot, and checks the witness by recomputing pub_snapshot from it. It MUST reject a witness of any other length. It checks one segment as verify_segment does, with the public labels, an empty ikm, and the witness heads in place of the stored ones. The epoch digest tree's ordering is strict, so it has snapshots ([I-D.sullivan-cfrg-raae]) at every count k at which the object is open. The formulas above with k in place of n_seg give snapshot(k) over d_0 through d_e and pub_snapshot(k) over pd_0 through pd_e, where e = (k-1) >> r, and the last head d_e, or pd_e, folds the leaves of epoch e present at k, with their count k - e * 2^r. A writer that produces intermediate snapshots holds one complete segment back until more content or the end arrives, and marks the last segment final when it encrypts it ([I-D.sullivan-cfrg-raae]). The leaves of each intermediate snapshot are then a prefix of those of every later snapshot, the final one included, as long as the writer neither re-marks nor rewrites a segment below a count at which it produced one. A reader that receives segments in index order checks each intermediate snapshot as it arrives. It forms each leaf from the segment bytes it holds and the segment's A_i, with is_final = 0, and with is_final = 1 at n_seg-1 when it checks the final value after that segment decrypts as final, never taking a stored or carried leaf or head in place of one it can compute. It keeps two running LH states, one over the current epoch's leaf run and one over the heads of the full epochs below it, both incremental because LH is the suite's Extract or XOF ([I-D.sullivan-cfrg-raae]). To check the value at k it finalizes a copy of the epoch-run state into the head of epoch e with its count, absorbs that head into a copy of the heads state, finalizes it, derives the value at k, and compares in constant time. At an epoch boundary it absorbs the full epoch's head into the heads state itself and starts the next epoch's run. A reader that joins late does so at an epoch boundary k = (e+1) * 2^r, where every head below k is full and never changes. It takes d_0 through d_e, or pd_0 through pd_e, exactly (e+1) * Nh octets, from the consuming protocol, derives the value at k from them, and compares it in constant time with the value published at k, which for pub_snapshot(k) is accepted only under the signed statement. On acceptance it adopts the heads as its running state and continues in order from segment k. It has authenticated a summary of the segments below k, not their ciphertext or their availability. Sullivan Expires 6 May 2027 [Page 35] Internet-Draft SEAL November 2026 Every leaf of an intermediate snapshot at count k is formed with is_final = 0, in the nonce field in derived nonce mode and in the segment AAD in random nonce mode, so the value fixes is_final = 0 at index k-1, and a verifier MUST NOT infer finality from the value's count. A consuming protocol that signs intermediate snapshots MUST define how a checkpoint is identified, its rollback policy, and any fork detection it claims. 6. Snapshot Authenticator Security Snapshot integrity instantiates the snapshot-forgery advantage Adv_snap of the master ra-ROR bound ([I-D.sullivan-cfrg-raae]) per snap_id: the None selection contributes zero, and each authenticator below contributes the terms its construction requires. Every keyed snapshot in this document is a KDF output over an intermediate value that commits to the segments: the accumulator with the count under the masked multiset hash, the LH digest of the encoded leaf run under the digest transcript, and the LH digest of the heads run under the epoch digest tree. Call that the committed value. Matching it at the same count matches the snapshot, because the KDF that follows is deterministic, so an adversary attacks the committed value rather than the snapshot. 6.1. Masked Multiset Hash SEAL's masked multiset hash publishes its keyed snapshot, a MAC over the count and accumulator under snap_key, and stores the masked accumulator wrapped_acc beside it as a summary. wrapped_acc is that accumulator hidden behind a deterministic, tag-derived one-time pad. The mask stops a write adversary that does not hold snap_key from recombining the differences between published accumulators into a non-historical segment set. A party that holds snap_key derives the mask and strips it. The three labels contrib_label, snapshot_label, and acc_mask_label are distinct under the encode frame. For snap_id 0x0001 the Adv_snap term of the master ra-ROR bound ([I-D.sullivan-cfrg-raae]) is bounded by a fresh-input MAC term, a mix-and-match term, and a birthday term in the number of published snapshots that the deterministic masking introduces, on no assumption beyond the multi-user PRF the key schedule already uses. Appendix B derives the value. Under this authenticator, SnapVerify recomputes the keyed snapshot from the observed count and the recomputed accumulator and compares it against the stored keyed snapshot in one constant-time comparison. Sullivan Expires 6 May 2027 [Page 36] Internet-Draft SEAL November 2026 A variable-time comparison would reveal the position of the first differing octet, letting an adversary who retries tampered inputs learn the expected keyed snapshot one octet at a time. 6.2. Digest Transcript SEAL's digest transcript publishes one keyed KDF evaluation over the payload_info elements, the commitment, the segment count, and the LH digest of the encoded leaf run (Section 5.2). Its transcript label is distinct under the encode frame, the encode framing of the run binds each leaf's position, and the count element binds the count. Without snap_key, a forgery is one of two events. The first is a fresh-input forgery of the keyed derivation, on the same multi-user PRF assumption as the rest of the key schedule. The second is a second preimage of LH at the leaf run, a multi-target term over the run under each published snapshot, since a different run with the same digest leaves the keyed snapshot unchanged. It is the term the epoch digest tree carries at its runs, and the long-message and sponge figures of Section 6.3 apply to it, with the run of n_seg leaves, the longest LH input of either authenticator, as the target. There is no accumulator to recombine, no mask, and no birthday term in the number of published snapshots. The leaf gives the transcript ciphertext binding and nonce binding against a key holder. Ciphertext binding holds only when the ciphertext digest is set, so the leaf carries LH(ct_i). LH(ct_i) ([I-D.sullivan-cfrg-raae]) is the ciphertext digest, and a fixed transcript binds the ordered leaf list under the collision resistance of the KDF over framed inputs and of LH over the encoded run, so the ciphertext binding rests on the collision resistance of LH for any segment length. Against a fixed published transcript, a substitution needs a second preimage at one of n_seg + 1 targets, the leaves and the run. Every input to LH is public, so this is a collision- resistance requirement, separate from the PRF assumption above, and it does not degrade when the adversary holds the CEK and every key derived from it. Without the ciphertext digest the transcript binds the tags, nonce fields, segment AAD, and order but not the ciphertext content, which suits a single-CEK-holder deployment. The digest transcript is still a symmetric authenticator: any snap_key holder can compute a valid snapshot for an object it writes itself. What a key holder cannot do without a collision is exhibit a different ordered leaf list that verifies under a fixed, already-published transcript value. Sullivan Expires 6 May 2027 [Page 37] Internet-Draft SEAL November 2026 In random nonce mode the leaf carries the stored nonce, so two nonces give two leaves. A fixed authenticated transcript fixes each index's nonce for every suite, independently of whether the AEAD tag is injective in the nonce. In derived nonce mode the leaf carries the counter (i << 1) | is_final, and the commitment fixes the CEK and payload_info, so the authenticated transcript fixes the nonce through the derivation for a decryptor that verifies the commitment. The digest transcript accepts either nonce mode for ciphertext, nonce, and AAD binding. With the ciphertext digest set, the leaf fixes the ciphertext, the tag, the nonce field, and the segment AAD, A_i included, so the plaintext-binding claims here need no external binding of A_i. The masked multiset hash gives no such binding, in two independent ways. Its contributions bind AEAD tags, and under an adversarially known CEK a non-committing AEAD admits a second ciphertext with the same tag ([I-D.sullivan-cfrg-raae]), so segment content can change under an unchanged accumulator. Separately, its ordering is linear: a party that knows snap_key evaluates contributions offline and solves for a different segment set with the same accumulator by the k-list XOR algorithm of [Wagner], at about 2^(8*Nh / (1 + floor(log2(n_seg)))) operations over n_seg lists. The floor is there because the k-tree needs a power-of-two list count, so a count between two powers gives the lower one's cost. That cost meets the digest transcript's 2^(4*Nh) ciphertext equivocation floor at n_seg = 2 and drops below it at n_seg = 4. The search needs many candidate contributions at each index, which a CEK holder obtains at unchanged plaintext by re-encrypting under fresh nonces in random nonce mode, or by varying padding in derived nonce mode, so the forged object decrypts to plaintext the CEK holder chose. Above 8*Nh segments two candidates per index suffice: the n_seg differences between them, each 8*Nh bits, are then linearly dependent, and Gaussian elimination finds a set of indices to switch that leaves the accumulator unchanged. Carrying the ciphertext digest in each contribution does not repair this. It does repair the first weakness: a contribution covering LH(ct_i) moves the accumulator whenever the ciphertext changes, so the substitution that was free under a non-committing AEAD now costs the search above. What it cannot do is change that cost, which the accumulator width and the segment count fix, not what each contribution is computed over. A linear authenticator therefore MUST NOT carry the ciphertext digest. Setting it is how an instantiation states that its segments are bound against a CEK holder (Section 3.1), and [I-D.sullivan-cfrg-raae] makes that binding hold under three Sullivan Expires 6 May 2027 [Page 38] Internet-Draft SEAL November 2026 conditions: the keyed snapshot is authenticated outside the construction, the leaf carries LH(ct_i), and the ordering is strict. A linear ordering fails the third condition, because the k-list search reaches a different segment set without disturbing the first two. The count that sets the search cost belongs to the object rather than to any row, which fixes segment_max and never n_seg. The binding thus weakens as an object grows, passing the digest transcript's ciphertext equivocation floor at four segments. The ordering is settled by snap_id (Section 11), so the prohibition binds the registrant of a snap_id rather than a protocol choosing among the ones defined here. The masked multiset hash accordingly folds the AEAD tag alone. Only a non-linear ordering, as the digest transcript and the epoch digest tree use, turns the search into a collision problem and holds the binding at every count. Neither observation weakens the masked multiset hash in its own threat model, which excludes key holders: snap_key is derived from the CEK ([I-D.sullivan-cfrg-raae]), so in a single-owner deployment no adversary holds snap_key. Both matter only when a keyed snapshot is lifted into a stronger statement. That lifting is the signing layer this section requires for sender attribution. A signature or MAC over a keyed snapshot carries content binding against CEK holders only when the underlying value already binds content against key holders. The digest transcript is designed for that use, and its commitment element carries the full object context (CEK, payload_info, and G) into the authenticated value, so a signed value binds one object context. An application MUST NOT rely on a signature over the masked multiset hash's keyed snapshot for content attribution among CEK holders. The ciphertext digest is what grounds content binding on collision resistance. With the ciphertext digest set, an authenticated digest transcript pins each index to one ciphertext, tag, nonce field, and segment AAD, even against a party that holds or chose the CEK, which gives the per-index binding of [I-D.sullivan-cfrg-raae] with its stated gap to ra-CMT-p. Sullivan Expires 6 May 2027 [Page 39] Internet-Draft SEAL November 2026 The public snapshot (Section 5.2) is the same framed derivation with an empty ikm and its own label, so its binding rests on collision and second-preimage resistance alone, with no PRF term, and it is not unforgeable: anyone computes it. In the two-step class the extract step then takes only public inputs, so the output at length Nh is HMAC under a known key over the framed input. An output collision on distinct inputs is then a collision of LH on the run or on an over- large element, or of the inner or the outer hash call. In the one- step class it is a collision of the XOF or of LH. A signed pub_snapshot therefore binds payload_info, the commitment, the count, and the ordered leaf list for a verifier that holds no key, as an authenticated keyed snapshot does for a key holder. The formal forgery and content-binding reductions for the digest transcript are deferred to the combined proof in preparation ([SEALPROOFS]), with SEAL's other snapshot proofs. 6.3. Epoch Digest Tree The epoch digest tree (Section 5.3) has the same per-segment leaf and the same key as the digest transcript, so its ciphertext, nonce, and AAD binding in both modes, and its per-index binding for every suite, with the same gap to ra-CMT-p, are those of Section 6.2. They hold for an intermediate snapshot (Section 5.3.1) too, whose leaves fix is_final = 0 at its last index. The two-level fold rests on the same collision-resistance assumption. An epoch head binds its epoch's leaf run through LH and the KDF, and the snapshot binds the epoch heads the same way, so a forged snapshot requires an LH or KDF collision at one of the two levels or a snap_key PRF forgery at either keyed level. Each leaf carries its index, and the snapshot binds the heads in order and binds n_seg and payload_info, so a head cannot move to another position and the segmentation is fixed by n_seg and epoch_length. Without snap_key, a forgery is one of three events. The first is a fresh-input forgery of a keyed digest, at the root or at an epoch head: the digest transcript's PRF term (Section 6.2), once at each keyed level, with no birthday term in the number of published snapshots. The second is a collision between two heads over different runs at the same epoch, a birthday term in the number of epoch runs folded under snap_key, n_ep plus one for each epoch head a change recomputes and one for each intermediate snapshot produced between epoch boundaries, whose last head is partial, at most about q_h^2 / 2^(8*Nh + 1) for q_h such heads. The header stores the full heads in the clear. A run whose head equals another run's head verifies in its place under an unchanged heads run, as at an earlier version of the same slot. A run formed at another epoch cannot take Sullivan Expires 6 May 2027 [Page 40] Internet-Draft SEAL November 2026 that place: the verifier forms each leaf with the index of the segment it checks, in the nonce field or the segment AAD (Section 5.3), so this leaf-index binding separates epochs, only pairs of runs at the same epoch count toward the term, and the figure is an upper bound. The third is a second preimage of LH at an epoch run or at the heads run, a multi-target term over the n_ep epoch runs and the heads run under each published snapshot, since a different run with the same digest leaves every keyed value above it unchanged. Against a key holder, and where the ciphertext digest is set, substituting ciphertext bytes under a fixed published snapshot requires an LH or KDF collision against that fixed target, a second preimage, at one of three target classes: the n_seg leaves shared with the digest transcript, the n_ep epoch heads, and the single root. The work factor is 2^(8*Nh) per target for an ideal hash, or 2^(c/2) for a sponge of capacity c, 2^256 for TurboSHAKE-256 at Nh = 64, with a benign multiplicity of n_seg + n_ep + 1. The two fold levels thus add epoch and root targets to the transcript's leaf targets without lowering the per-target strength. When LH is HMAC over a Merkle-Damgard hash whose chaining value is Nh octets, SHA-256 and SHA-512, a long-message second preimage lowers the work to about 2^(8*Nh) divided by the total number of compression blocks across the targets. SHA-384's 64-octet chaining value leaves the generic figure in force below 2^128 blocks. A writer that chooses different ciphertext or leaf lists for both openings of one snapshot instead faces a birthday collision at 2^(4*Nh), the same ciphertext equivocation floor as the digest transcript, which the fold does not lower. The public snapshot's two levels bind as the keyed levels do, by the collision resistance of LH and of the KDF under a public key (Section 6.2), with no PRF term. Every party is then in the key holder's position above: a different leaf list or count under a fixed, authenticated pub_snapshot needs a second preimage at a leaf, a public head, or the root, and a signer that wants two openings of one value faces the birthday collision at 2^(4*Nh). A substituted witness head leaves the recomputed pub_snapshot unchanged only through a collision at the root. The LH digests at every level are unkeyed and shared by every object, for the keyed value as for the public one, so the accounting is aggregate over objects, epochs, intermediate snapshots, and internal digests. The commitment and payload_info in each head, and the leaf indices in each run, separate only the heads themselves, for both values, and the long-message and sponge figures above apply to the public runs as to the keyed ones. Sullivan Expires 6 May 2027 [Page 41] Internet-Draft SEAL November 2026 Changing a nonce field or a segment AAD changes the leaf, so the authenticated snapshot fixes both, and per-index binding holds in both nonce modes for every suite under the conditions of Section 6.2. The formal two-level reduction, the fresh-input forgery term at either keyed level and the LH and KDF terms at the epoch and root levels, is deferred to the combined proof in preparation ([SEALPROOFS]), with the digest transcript's. This authenticator's verify_segment compares recomputed values against stored ones at three levels, the snapshot, the epoch head, and the leaf, and MUST use a constant-time octet comparison at each. The stored value compared against at each level is public metadata, and snap_key enters only through the KDF, whose constant-time requirement is in [I-D.sullivan-cfrg-raae], so no comparison carries a secret. Because the reference operands are public, the fused single-comparison rule of the masked multiset hash (Section 6.1) does not apply: a per-level early return reveals only which stored region a tampering party altered, which that party already knows, and leaks no keyed value. 7. SEAL Named Instantiations SEAL has many parameters ([I-D.sullivan-cfrg-raae]). A relying protocol that does not want to choose them all can cite one of the named instantiations in the table below. Each row fixes a profile, a segment size, the nonce mode, a snapshot authenticator, and one of the serialization layouts of Section 4, leaving the cipher suite (an aead_id and a kdf_id from Section 3) to the referencing protocol. Each instantiation applies to a cipher suite the same way, so a protocol writes SEAL-simple(aead_id, kdf_id), SEAL-disk(aead_id, kdf_id), and so on, to obtain a complete raAE scheme. Sullivan Expires 6 May 2027 [Page 42] Internet-Draft SEAL November 2026 +================+===========+=======+===========+==========+=====+=======+=======+==========+ |instantiation_id|Name |Profile|segment_max|nonce_mode|epoch|snap_id|layout |ciphertext| | | | | | | | | |digest | +================+===========+=======+===========+==========+=====+=======+=======+==========+ |0x0001 |SEAL-simple|SEAL- |65536 |derived |32 |0x0000 |linear |no | | | |RO-v1 | | | | | | | +----------------+-----------+-------+-----------+----------+-----+-------+-------+----------+ |0x0002 |SEAL- |SEAL- |65536 |derived |10 |0x0003 |aligned|yes | | |attachment |RO-v1 | | | | | | | +----------------+-----------+-------+-----------+----------+-----+-------+-------+----------+ |0x0003 |SEAL- |SEAL- |65536 |derived |10 |0x0002 |aligned|yes | | |attachment-|RO-v1 | | | | | | | | |small | | | | | | | | +----------------+-----------+-------+-----------+----------+-----+-------+-------+----------+ |0x0004 |SEAL- |SEAL- |65536 |random |16 |0x0001 |linear |no | | |editable |RW-v1 | | | | | | | +----------------+-----------+-------+-----------+----------+-----+-------+-------+----------+ |0x0005 |SEAL-memory|SEAL- |16384 |random |16 |0x0001 |aligned|no | | | |RW-v1 | | | | | | | +----------------+-----------+-------+-----------+----------+-----+-------+-------+----------+ |0x0006 |SEAL-disk |SEAL- |16384 |random |16 |0x0001 |split |no | | | |RW-v1 | | | | | | | +----------------+-----------+-------+-----------+----------+-----+-------+-------+----------+ |0x0007 |SEAL- |SEAL- |16384 |derived |16 |0x0001 |aligned|no | | |compact |RW-v1 | | | | | | | +----------------+-----------+-------+-----------+----------+-----+-------+-------+----------+ |0x0008 |SEAL-log |SEAL- |16384 |derived |16 |0x0000 |append-|no | | | |AO-v1 | | | | |log | | +----------------+-----------+-------+-----------+----------+-----+-------+-------+----------+ Table 10: SEAL named instantiations The instantiation_id column is a uint16 code point naming the instantiation as a whole. No field defined in this document carries an instantiation_id. An instantiation is cited by name, and its parameters reach the key schedule individually through payload_info ([I-D.sullivan-cfrg-raae]), which carries aead_id, kdf_id and snap_id but nothing identifying the instantiation they came from. The code point exists so that a protocol negotiating an instantiation need not mint an identifier of its own. The snap_id column selects the snapshot authenticator within the profile's admitted set (Section 3.2). The ciphertext digest column is a payload_info choice independent of snap_id ([I-D.sullivan-cfrg-raae]), and each instantiation pins it: the attachment instantiations set it, over their strict authenticators, and the others do not. Sullivan Expires 6 May 2027 [Page 43] Internet-Draft SEAL November 2026 Every instantiation uses commitment_length = Nh, a fresh 32-octet salt per object, and the epoch_length in its row ([I-D.sullivan-cfrg-raae]): 32 for the whole-file SEAL-simple, 10 for both SEAL-attachment rows, and 16 for the mutable rows and SEAL-log, a conservative value. The mutable rows spend that per-epoch-key headroom on rewrites. SEAL-log rewrites a segment only by its one re-mark, so its 16 bounds how many segments share an epoch key, plus the one extra derived nonce each re-mark draws. The two SEAL- attachment rows spend their 10 differently. SEAL-attachment selects the epoch digest tree, which reuses epoch_length as its grouping fan- out (Section 5.3), so 10 is the Table 9 value. SEAL-attachment-small selects the flat digest transcript, which has no grouping, so there 10 is key rotation alone. A row's epoch_length is a choice, so the cipher suite's default epoch_length ([I-D.sullivan-cfrg-raae]) does not apply to a named instantiation. That default fills a gap where no value is chosen, so the row's value stands even under a 256-bit-nonce suite, whose nonce size would otherwise make a flat key the natural pick. A referencing protocol MAY override the row's value. Because a row fixes epoch_length while leaving the KDF open, a suite with Nh = 64 puts SEAL-attachment outside the Table 9 guidance, which gives 9 there rather than 10: 2^10 * (Nh + Nt) is 81920, above the 65536 segment_max. The epoch digest tree stays correct, and an epoch head then costs two aligned reads rather than one. A protocol that wants the single read back pairs SEAL-attachment with an Nh of 32 or 48, or sets epoch_length to 9. The consuming protocol pins the remaining serialization details for byte-level interoperability (Section 4). SEAL-simple is for write-once content read whole: no snapshot authenticator, in the reduced immutable linear layout (Section 4.5). SEAL-attachment is for write-once content whose segments a reader verifies or decrypts individually. It requires the epoch digest tree (snap_id 0x0003), and an object under SEAL-attachment MUST carry its keyed snapshot. Its aligned layout (Section 4.2) lets a reader verify one segment in two aligned metadata reads plus the segment itself, without reading the others when the epoch's A_i are empty or at hand, for objects up to about 128 GiB at the recommended parameters (Table 9). Sullivan Expires 6 May 2027 [Page 44] Internet-Draft SEAL November 2026 SEAL-attachment-small is the same for small write-once content, using the flat digest transcript (snap_id 0x0002) instead of the epoch digest tree: the reader checks one segment against the full leaf list rather than one epoch's run, a fit when the leaf list is one read anyway or the consumer verifies every segment on the way through. SEAL-editable is the basic mutable object with whole-object integrity from the masked multiset hash (Section 5.1), stored in the linear layout (Section 4.1). SEAL-memory targets in-memory random access with the aligned layout (Section 4.2). SEAL-disk rewrites individual segments on stored media, with the split layout (Section 4.3) holding the headers apart from the segment data so a rewrite touches one segment and extension appends to both streams. SEAL-compact is random-access mutable in the aligned layout with a derived nonce, recomputed rather than stored: each per-segment metadata entry is Nt octets instead of Nn + Nt (Section 4.2), saving Nn octets per segment across the large objects the aligned layout targets. The in-place rewrite reuses that nonce, so it requires an MRAE AEAD and is deterministic. For the same reason the writer takes every tag an update removes, in a rewrite, a re-mark, or a truncation, from locally trusted state or recomputes the snapshot from every tag (Section 5.1). SEAL-log is for a growing archive or log, appended and read in order. It selects the append-only profile SEAL-AO-v1 with a derived nonce and the append-log layout (Section 4.4), which stores each ciphertext length so the segment left short by a re-mark can be an interior one. It selects a null snapshot, so integrity is per-segment: a reader authenticates each segment at its index, but the current segment set is not bound, and truncation or rollback to an earlier honest length is out of scope, for which a consuming protocol supplies freshness (Section 3.2). As under every snap_id, the stored log gives an appender no current count, so a SEAL-log writer keeps its own count or obtains a fresh one before it appends ([I-D.sullivan-cfrg-raae]). 8. Binding Limits for Profiled Suites For each suite and nonce mode it lists, the table identifies the binding usage-limit class of [I-D.sullivan-cfrg-raae]. Numerical entries are single-key, single-term figures at 2^-32, except the AES- GCM random-nonce budget, which AES-GCM's own specification caps at 2^32 encryptions per key, here the epoch key, rewrites included ([NIST-SP-800-38D] Section 8.3). All are subject to the AEAD's full analysis and are not aggregate allowances ([I-D.sullivan-cfrg-raae]). Sullivan Expires 6 May 2027 [Page 45] Internet-Draft SEAL November 2026 +==================+=============+==================+===============+ |Suite / mode |Binding limit|Budget |Failure mode | +==================+=============+==================+===============+ |AES-128-GCM, |random-nonce |at most 2^32 per |confidentiality| |random |collision |epoch key |and integrity | | | |(specification | | | | |cap) | | +------------------+-------------+------------------+---------------+ |AES-256-GCM, |random-nonce |at most 2^32 per |confidentiality| |random |collision |epoch key |and integrity | | | |(specification | | | | |cap) | | +------------------+-------------+------------------+---------------+ |ChaCha20-Poly1305,|random-nonce |~2^32 per epoch |confidentiality| |random |collision |key |and integrity | +------------------+-------------+------------------+---------------+ |AES-256-GCM-SIV, |derived-nonce|workload-dependent|confidentiality| |derived, distinct |MRAE workload|multi-user bound |and integrity | |segments | | | | +------------------+-------------+------------------+---------------+ |AES-256-GCM-SIV, |derived-nonce|workload-dependent|confidentiality| |derived, hot |MRAE workload|multi-user bound |and integrity | |segment | | | | +------------------+-------------+------------------+---------------+ |AEGIS-256, random |forgery bound|~2^83 forgery |integrity | | | |attempts per key | | +------------------+-------------+------------------+---------------+ |AEGIS-256X2, |forgery bound|~2^83 forgery |integrity | |random | |attempts per key | | +------------------+-------------+------------------+---------------+ Table 11: Binding limits for profiled suites, length-dependent figures at 64 KiB The random-nonce counts depend on nonce size. Data-volume and forgery limits also depend on segment length and effective associated data, under the full block accounting of the applicable AEAD analysis. AEGIS makes nonce collision negligible with a 256-bit nonce (~2^112) and binds on forgery. For AES-256-GCM-SIV, the data-volume term couples total processed blocks across all keys and nonces to the maximum encryption blocks at any one (key, nonce) pair ([I-D.sullivan-cfrg-raae]). Sullivan Expires 6 May 2027 [Page 46] Internet-Draft SEAL November 2026 8.1. Write Limits by Instantiation Once a named instantiation (Section 7) fixes the segment size, the nonce mode, and the epoch_length, the classes above determine how a deployment computes its write limits. The table below gives per-key random-nonce illustrations; an aggregate target can bind sooner. Epoch rotation leaves the structural ceiling at 2^63 segments (about 2^79 octets at segment_max 65536, 2^77 at 16384), but does not guarantee that the aggregate security budget permits reaching it. Initial writes and rewrites both consume that budget ([I-D.sullivan-cfrg-raae]). For a random-nonce suite the epoch key holds one shared nonce- collision pool of about 2^32 segment encryptions ([I-D.sullivan-cfrg-raae]). Rewriting a single hot segment while the rest stay put can spend most of that pool on the one segment, so its ceiling is about 2^32. When the 2^epoch_length segments under the key are rewritten evenly the pool divides among them, so each takes about 2^(32 - epoch_length) on average: this is the bound in force when the epoch key, not a single segment, is the limiting factor. These are per-key nonce-collision figures: K equally busy epoch keys divide the same aggregate allocation by K, reducing both rewrite figures by sqrt(K). For AES-256-GCM-SIV the hot and average limits depend on the full workload ([I-D.sullivan-cfrg-raae]). For the data-volume term alone, H equally busy (key, nonce) pairs reduce the per-pair count by sqrt(H) relative to one isolated hot pair. Rewriting every non- terminal segment in a full epoch uses about 2^epoch_length pairs, so at epoch_length 16 this scaling reduces the average by about 2^8. Other epochs, objects, and initial writes also enter the aggregate bound. Sullivan Expires 6 May 2027 [Page 47] Internet-Draft SEAL November 2026 +=====================+=======+==================+==================+ |Instantiation |Segment|Rewrites per hot |Avg rewrites per | | |size |segment |segment | +=====================+=======+==================+==================+ |SEAL-simple |64 KiB |write-once |write-once | +---------------------+-------+------------------+------------------+ |SEAL-attachment |64 KiB |write-once |write-once | +---------------------+-------+------------------+------------------+ |SEAL-attachment-small|64 KiB |write-once |write-once | +---------------------+-------+------------------+------------------+ |SEAL-editable |64 KiB |~2^32 |~2^16 | +---------------------+-------+------------------+------------------+ |SEAL-memory |16 KiB |~2^32 |~2^16 | +---------------------+-------+------------------+------------------+ |SEAL-disk |16 KiB |~2^32 |~2^16 | +---------------------+-------+------------------+------------------+ |SEAL-compact |16 KiB |workload-dependent|workload-dependent| +---------------------+-------+------------------+------------------+ |SEAL-log |16 KiB |one re-mark |one re-mark | +---------------------+-------+------------------+------------------+ Table 12: Qualified segment rewrite limits The figures are for the 96-bit-nonce suites (AES-GCM, ChaCha20-Poly1305) at the epoch_length 16 these instantiations fix; a smaller epoch_length raises the average. They follow from the 12-octet random nonce, not from the AEAD's key, tag, or block size: at these segment sizes random-nonce collision binds before the block- size term ([I-D.sullivan-cfrg-raae]), so any 96-bit-nonce suite gives the same figures. For AES-GCM its specification cap (Section 8) gives the same figures as ceilings. Two cases depart from them. An AEGIS suite binds on the forgery bound, which counts verification attempts rather than rewrites. A derived-nonce MRAE suite (AES-256- GCM-SIV, as SEAL-compact takes) uses the coupled multi-user bound, and absent an allocation the per-object rule of [I-D.sullivan-cfrg-raae] sets its limit. SEAL-log rewrites a segment only by its one re-mark, under a new nonce, since SEAL-AO-v1 derives a fresh nonce for each index and finality bit. In random nonce mode a repeated nonce under AES-GCM or ChaCha20-Poly1305 also exposes an authentication key ([I-D.sullivan-cfrg-raae]), so for those suites the collision term counts against integrity as well. The per-query forgery term counts verification attempts. Its limits come from [I-D.irtf-cfrg-aead-limits] for the GCM and ChaCha20-Poly1305 suites and [RFC8452] for AES-256-GCM-SIV, and run from about 2^59 to 2^83 forgery attempts at a single-key, forgery-term 2^-32 target. Under AES-256-GCM-SIV an attempt's blocks also enter the coupled data- Sullivan Expires 6 May 2027 [Page 48] Internet-Draft SEAL November 2026 volume term, which charges each attempt about C * B / 2^128 for every block it processes, with B the largest encryption load at any one pair ([I-D.sullivan-cfrg-raae]). The AEGIS analysis states at least 128-bit forgery security with no per-query formula, so the AEGIS rows take the AES-GCM figure of about 2^83 as a conservative floor. Aggregate integrity accounting counts all attempts across keys and includes the AEAD's further terms ([I-D.sullivan-cfrg-raae]). 9. Streaming with Prefix Verification This section is informative. A live publisher that holds the CEK streams segments to many subscribers that also hold it, through relays that do not. No layout of Section 4 holds an epoch digest tree object while it is open: the append-log layout admits no snapshot authenticator, and the others need the final count to size the header. The consuming protocol therefore frames the object's payload_info and commitment, the segments, each as its ciphertext, metadata entry, and any A_i, and the values. That framing, the signing key, and the cadence belong to the consuming protocol. The configuration is the epoch digest tree (Section 5.3), in either nonce mode, with epoch_length chosen so that 2^r segments span the interval at which a subscriber may join. The consuming protocol sets the playback window, the delay a reader accepts between playing a segment and verifying it. The publisher follows the writer discipline of Section 5.3.1. At each epoch boundary, and at the count k that ends each playback window, it publishes, in the consuming protocol's framing, the keyed value snapshot(k) for subscribers and pub_snapshot(k), in the signed statement of [I-D.sullivan-cfrg-raae], for relays and for subscribers that need its origin, and it keeps the full heads available to late joiners. A subscriber plays the segments of the current window on per-segment authenticity alone, treats that content as not yet verified, and checks snapshot(k) at each published count as the in-order reader of Section 5.3.1 does. On rejection it stops and reports, and it cannot recall what it has already played. A subscriber that joins late continues from the latest boundary as Section 5.3.1 describes for a reader that joins at k. A relay holds no CEK. It verifies the signed statement and recomputes pub_snapshot(k) from the leaves of the segments it forwards and their A_i (Section 5.3.1). Sullivan Expires 6 May 2027 [Page 49] Internet-Draft SEAL November 2026 Beyond the limits Section 5.3.1 states, the values establish neither of the following, and a consuming protocol that needs either provides it itself: * Withholding or delay of segments or values. The reader cannot tell one from the other. The consuming protocol sets cadence and timeouts. * Content played before a check that later fails. 10. Security Considerations The security analysis of SEAL is in the companion ([I-D.sullivan-cfrg-raae]): the ra-ROR joint confidentiality and authenticity target, the ra-CMT commitment target, the reduction to the underlying AEAD and KDF, and the concrete bounds and budgets. This document adds the per-authenticator forgery analysis (Section 6) and the profiled limits (Section 8). With the ciphertext digest set and an externally authenticated snapshot, the strict authenticators give per-index binding in both nonce modes for every suite, for any A_i (Section 6.2, Section 6.3). The leaf carries the stored nonce in random nonce mode, the counter in derived nonce mode, and the segment AAD in both. This does not establish ra-CMT-p. Public snapshots (Section 5.2, Section 5.3.1) authenticate nothing on their own. A verifier accepts one only under the signed statement [I-D.sullivan-cfrg-raae] requires, parses under the payload_info parameters it carries, and enforces the witness length. Public acceptance binds ciphertext through the ciphertext digest that [I-D.sullivan-cfrg-raae] requires wherever a public snapshot is relied on, and establishes neither decryption nor any link between the commitment and a CEK. Keyed and public acceptance are independent, so a key holder that claims the signer's origin checks both. Public binding rests on collision and second-preimage resistance alone (Section 6.2), with the accounting aggregated over objects, epochs, intermediate snapshots, and internal digests (Section 6.3), and a public snapshot is not hiding. An intermediate snapshot authenticates a prefix only: completion needs a signed statement marking the value final and, for a decryptor, the terminal segment verifying as final. A signature prevents neither replay nor withholding nor equivocation, and a consuming protocol states the freshness and consistency it claims. The masked multiset hash defines no public snapshot and no intermediate snapshot. Sullivan Expires 6 May 2027 [Page 50] Internet-Draft SEAL November 2026 The salt is public object metadata. Confidentiality and integrity in the single-owner regime assume secrecy of the CEK and derived keys, not secrecy of the salt. The companion's KDF assumptions ([I-D.sullivan-cfrg-raae]) instantiate on the named suites as follows. For the HKDF-based suites, the PRF assumptions are placed on HKDF keyed by the CEK, payload_key, snap_key, or, under hedged randomness, sk and hedge_key, and the collision-resistance assumption on the commit derivation reduces to the collision resistance of the underlying SHA-2 hash. Because every SEAL KDF output is at most Nh octets, HKDF-Expand makes a single HMAC call per derivation, and Section 5 of [RFC5869] refers to the published analysis of HKDF. For TurboSHAKE-256 ([RFC9861]), the same assumptions are placed on the XOF as instantiated by its HPKE KDF registry entry, which fixes the primitive and its parameters but carries no security assumptions of its own. A private-use snap_id carries a specific hazard. snap_id is a payload_info member ([I-D.sullivan-cfrg-raae]), so two deployments that assign the same private value to different authenticators agree on payload_info and derive the same commitment, payload_key, and snap_key. The commitment catches a wrong parameter context (Section 3.2) and precedes a reader's SnapVerify, so the check that would have caught the mismatch passes, and whatever rejects the object later reports some other reason rather than the collision. 10.1. Resource Exhaustion The resource-exhaustion considerations of [I-D.sullivan-cfrg-raae] apply. The layouts add to them: the per-segment metadata and aux regions grow with segment or epoch counts, so consuming protocols MUST also limit metadata and aux sizes, and the leaf lists and witness heads they carry for public verification (Section 5.3.1). The overflow and bounds checks the companion requires cover, in these layouts, multiplication by segment_max or metadata entry sizes and addition of header and aux lengths. Append-log ciphertext lengths MUST be checked against segment_max, and the ciphertext plus metadata MUST fit within the available input before reading a record. 11. IANA Considerations The aead_id and kdf_id code points that SEAL cipher suites use are existing entries in the IANA AEAD Algorithms registry ([RFC5116]) and the HPKE KDF registry ([RFC9180]). This document does not modify either registry. Sullivan Expires 6 May 2027 [Page 51] Internet-Draft SEAL November 2026 This document requests two new registries. Both use uint16 code points, and both apply Specification Required ([RFC8126]) to 0x0000 through 0xFEFF, reserving 0xFF00 through 0xFFFF for private and experimental use. The reservation carries different weight in each. A private-use snap_id reaches the key schedule, so it needs the rule stated below. A private-use instantiation_id reaches nothing this document defines, and is reserved so that a protocol which does put one on its own wire has a range that no future registration will collide with. The first is "SEAL Named Instantiations". An entry fixes a profile, a maximum segment size, a nonce mode, an epoch length, a snapshot authenticator, a serialization layout, and a ciphertext-digest setting, and leaves the cipher suite to the referencing protocol (Section 7). The registry has the columns of Table 10, namely instantiation_id, Name, Profile, segment_max, nonce_mode, epoch, snap_id, layout, and ciphertext digest, plus a Reference column. Its initial contents are the entries of Table 10, each referencing this document. The second is "SEAL Snapshot Authenticators", which assigns the snap_id values named in Table 3. Its columns are snap_id, Name, Na, ordering, summary, and Reference, so it records more than that table does. Na is the authenticator's keyed snapshot length. A summary is any state an authenticator derives from the per-segment inputs and keeps so that an update reads fewer of them, and it enables no function that recomputing from the inputs would not. The ordering column records whether a party holding snap_key can find a second segment set with the same keyed snapshot below the cost of a collision: linear if the construction offers a route below that cost, for example by linear algebra or by a generalized birthday search, strict if finding one is a collision problem, and n/a for an authenticator that produces no keyed snapshot. The analysis behind each value is in Section 6. The ordering also decides whether the authenticator has a public snapshot and intermediate snapshots ([I-D.sullivan-cfrg-raae]). The value a registrant chooses here settles the ciphertext-digest prohibition of Section 6.2, without this document naming the authenticator. Where Na or summary holds a size rather than a constant, it holds it as an expression: Nh is fixed by the cipher suite the referencing protocol picks, and n_ep varies per object. The authenticator's construction fixes the form, which is what the registry records. Sullivan Expires 6 May 2027 [Page 52] Internet-Draft SEAL November 2026 Each authenticator's summary, when it has one, is stored in the aux region of the layouts (Section 4.2), so Na is Nh under every authenticator here that produces a keyed snapshot. The initial contents are: +=========+============+====+==========+==============+===========+ | snap_id | Name | Na | ordering | summary | Reference | +=========+============+====+==========+==============+===========+ | 0x0000 | none | 0 | n/a | none | This | | | | | | | document | +---------+------------+----+----------+--------------+-----------+ | 0x0001 | masked | Nh | linear | accumulator, | This | | | multiset | | | masked | document | | | hash | | | | | +---------+------------+----+----------+--------------+-----------+ | 0x0002 | digest | Nh | strict | none | This | | | transcript | | | | document | +---------+------------+----+----------+--------------+-----------+ | 0x0003 | epoch | Nh | strict | n_ep * Nh | This | | | digest | | | epoch heads | document | | | tree | | | | | +---------+------------+----+----------+--------------+-----------+ Table 13: Initial SEAL Snapshot Authenticators registry The ordering column is unrelated to the linear serialization layout of Section 4.1, which names a way of arranging bytes rather than a way of combining inputs. A private-use snap_id is not interoperable and MUST NOT appear in an object intended for exchange (Section 10). The following code points are drawn from IANA registries. AEGIS-256 (0x0021) and AEGIS-256X2 (0x0024) are already registered in the AEAD Algorithms registry per [RFC10032]. TurboSHAKE-256 (0x0013) is registered in the HPKE KDF registry with [I-D.ietf-hpke-pq] as its reference, which becomes an RFC reference when that draft publishes. The SEAL normative reference to [I-D.ietf-hpke-pq] is a reference to a work in progress, so publication of this document waits on it. Sullivan Expires 6 May 2027 [Page 53] Internet-Draft SEAL November 2026 +=================+============+================+=================+ | Registry | Code point | Algorithm | Status | +=================+============+================+=================+ | AEAD Algorithms | 0x0021 | AEGIS-256 | assigned | +-----------------+------------+----------------+-----------------+ | AEAD Algorithms | 0x0024 | AEGIS-256X2 | assigned | +-----------------+------------+----------------+-----------------+ | HPKE KDF | 0x0013 | TurboSHAKE-256 | assigned, draft | | | | | reference | +-----------------+------------+----------------+-----------------+ Table 14: Registered Code Points Future SEAL profiles MAY consume additional entries from the AEAD Algorithms and HPKE KDF registries without revising this document. 11.1. Designated Expert Review For both registries, designated experts review new registrations and changes to existing entries under the Specification Required policy of [RFC8126]. Experts MUST require a stable, publicly available specification that defines every registry field unambiguously and is sufficient for independent interoperable implementations. A changed definition is a new registration under a new code point and, for an instantiation, a new name. For named instantiations, experts MUST check that the profile, segment size, nonce mode, epoch length, snap_id, layout, and ciphertext digest form a valid combination. For snapshot authenticators, the specification MUST define: * the complete input encoding, * snapshot computation and verification, * supported update operations, each with the witness it takes, how the writer checks that witness, and any removed input that may be used without a check; * any subset verification or summary state; and, * where its ordering is strict, it MUST define its public snapshot and its intermediate snapshots ([I-D.sullivan-cfrg-raae]). Experts MUST independently assess the requested ordering classification using the ordering definition of Section 11 and Section 6. The analysis MUST consider an adversary holding snap_key Sullivan Expires 6 May 2027 [Page 54] Internet-Draft SEAL November 2026 and choosing both segment sets. Experts MUST reject a requested strict classification whose analysis does not justify it, and MUST check the resulting ciphertext-digest restrictions in Section 6.2. 12. References 12.1. Normative References [I-D.ietf-hpke-pq] Barnes, R. and D. Connolly, "Post-Quantum and Post- Quantum/Traditional Hybrid Algorithms for HPKE", Work in Progress, Internet-Draft, draft-ietf-hpke-pq-05, 6 July 2026, . [I-D.sullivan-cfrg-raae] Sullivan, N., "Random-Access Authenticated Encryption", Work in Progress, Internet-Draft, draft-sullivan-cfrg- raae-04, 2026, . [NIST-SP-800-38D] Dworkin, M., "Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC", NIST Special Publication 800-38D, November 2007, . [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, . [RFC5116] McGrew, D., "An Interface and Algorithms for Authenticated Encryption", RFC 5116, DOI 10.17487/RFC5116, January 2008, . [RFC5869] Krawczyk, H. and P. Eronen, "HMAC-based Extract-and-Expand Key Derivation Function (HKDF)", RFC 5869, DOI 10.17487/RFC5869, May 2010, . [RFC8126] Cotton, M., Leiba, B., and T. Narten, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 8126, DOI 10.17487/RFC8126, June 2017, . Sullivan Expires 6 May 2027 [Page 55] Internet-Draft SEAL November 2026 [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, May 2017, . [RFC8439] Nir, Y. and A. Langley, "ChaCha20 and Poly1305 for IETF Protocols", RFC 8439, DOI 10.17487/RFC8439, June 2018, . [RFC8452] Gueron, S., Langley, A., and Y. Lindell, "AES-GCM-SIV: Nonce Misuse-Resistant Authenticated Encryption", RFC 8452, DOI 10.17487/RFC8452, April 2019, . [RFC9180] Barnes, R., Bhargavan, K., Lipp, B., and C. Wood, "Hybrid Public Key Encryption", RFC 9180, DOI 10.17487/RFC9180, February 2022, . [RFC9861] Viguier, B., Wong, D., Ed., Van Assche, G., Ed., Dang, Q., Ed., and J. Daemen, Ed., "KangarooTwelve and TurboSHAKE", RFC 9861, DOI 10.17487/RFC9861, October 2025, . [RFC10032] Denis, F. and S. Lucas, "The AEGIS Authenticated Encryption Algorithms", RFC 10032, DOI 10.17487/RFC10032, September 2026, . 12.2. Informative References [DAE] Rogaway, P. and T. Shrimpton, "A Provable-Security Treatment of the Key-Wrap Problem", EUROCRYPT 2006, LNCS 4004, pp. 373-390, DOI 10.1007/11761679_23, 2006, . [I-D.irtf-cfrg-aead-limits] Günther, F., Thomson, M., and C. A. Wood, "Usage Limits on AEAD Algorithms", Work in Progress, Internet-Draft, draft- irtf-cfrg-aead-limits-13, 3 September 2026, . [MSetHash] Clarke, D., Devadas, S., van Dijk, M., Gassend, B., and G. E. Suh, "Incremental Multiset Hash Functions and Their Application to Memory Integrity Checking", ASIACRYPT 2003, LNCS 2894, pp. 188-207, DOI 10.1007/978-3-540-40061-5_12, 2003, . Sullivan Expires 6 May 2027 [Page 56] Internet-Draft SEAL November 2026 [SEALPROOFS] Sullivan, N., "Security Analysis of the SEAL Construction: raAE, Snapshot Integrity, and Commitment", Work in Progress manuscript in preparation. [Wagner] Wagner, D., "A Generalized Birthday Problem", CRYPTO 2002, LNCS 2442, pp. 288-304, DOI 10.1007/3-540-45708-9_19, 2002, . Appendix A. SEAL-simple Implementation Sketch This appendix is informative. It specializes [I-D.sullivan-cfrg-raae] for SEAL-simple (Section 7). +===================+===========================================+ | Field | Value | +===================+===========================================+ | protocol_id | "SEAL-RO-v1" | +-------------------+-------------------------------------------+ | aead_id | 0x0002 (AES-256-GCM) | +-------------------+-------------------------------------------+ | kdf_id | 0x0001 (HKDF-SHA-256) | +-------------------+-------------------------------------------+ | snap_id | 0x0000 (no snapshot authenticator) | +-------------------+-------------------------------------------+ | nonce_mode | 0x01 (derived) | +-------------------+-------------------------------------------+ | epoch_length | 32 | +-------------------+-------------------------------------------+ | segment_max | 65536 | +-------------------+-------------------------------------------+ | Nk | 32 | +-------------------+-------------------------------------------+ | Nn | 12 | +-------------------+-------------------------------------------+ | Nt | 16 | +-------------------+-------------------------------------------+ | Nh | 32 | +-------------------+-------------------------------------------+ | commitment_length | 32 | +-------------------+-------------------------------------------+ | layout | linear, immutable reduction (Section 4.5) | +-------------------+-------------------------------------------+ Table 15: Fixed parameters for SEAL-simple(HKDF-SHA-256, AES- 256-GCM) Sullivan Expires 6 May 2027 [Page 57] Internet-Draft SEAL November 2026 Per-segment associated data A_i is empty throughout this sketch, and a caller that needs a non-empty A_i follows the general segment-AAD form defined in [I-D.sullivan-cfrg-raae]. Three separate quantities bound how large an object under this profile can grow. At an aggregate 2^-32 target the confidentiality term binds first, at no more than about 2^57 octets. * Integrity: 512 ZiB (2^79 octets) of ciphertext has total forgery probability at most ~2^-52, from the AES-256-GCM tag-forgery formula 2 x v x (n_blk + 1) / 2^128 at v = 2^63 segments and n_blk = 2^12 blocks per segment ([I-D.sullivan-cfrg-raae]). * Confidentiality: with epoch_length 32, each full epoch encrypts 2^32 segments of 2^12 blocks. The companion's block-size bound gives about 2^-41 per epoch key. Summing over all 2^31 keys at the structural ceiling gives about 2^-10 for this term, exceeding an aggregate 2^-32 target. Allocating 2^-32 to this term alone permits about 2^41 segments (2^57 octets) at this epoch_length; the full target also reserves advantage for the other terms ([I-D.sullivan-cfrg-raae]). * Structural: 512 ZiB (2^79 octets) is the object ceiling, from derived-nonce framing capping the segment index at 2^63 across up to 2^31 epoch keys ([I-D.sullivan-cfrg-raae]). The two blocks below use the linear layout (Section 4.1) and apply to either suite: aead_id, kdf_id, commitment_length, Nk, Nn, and the AEAD are read from the chosen suite's parameter table, and the rest is common to both. Each KDF(...) call is that suite's KDF combiner of [I-D.sullivan-cfrg-raae] (two-step for HKDF, one-step for the XOF), evaluated at protocol_id = "SEAL-RO-v1" with the derivation label as its leading argument. The salt draw, nonce derivation, and finality check follow the companion's procedures ([I-D.sullivan-cfrg-raae]). Encryption derives the message schedule once, then seals each segment under its epoch key and derived nonce: Sullivan Expires 6 May 2027 [Page 58] Internet-Draft SEAL November 2026 inputs: CEK (32 octets) salt (32 octets, fresh uniformly random draw per object) G (octet string, empty by default) P_0, ..., P_{n-1} (segment plaintexts; every non-final P_i is exactly 65536 octets, only the final P_i may be shorter) payload_info = [uint16(aead_id), ;; from the suite table uint32(65536), ;; segment_max_be uint16(kdf_id), ;; from the suite table uint16(0x0000), ;; snap_id uint8(0x00), ;; segment_commitment = not set uint8(0x01), ;; nonce_mode = derived uint8(32), ;; epoch_length_u8 salt] ;; 32 octets commitment = KDF("commit", [CEK], [...payload_info, G], commitment_length) payload_key = KDF("payload_key", [CEK], payload_info, Nk) nonce_base = KDF("nonce_base", [CEK], payload_info, Nn) for i in 0 .. n-1: is_final = 1 if i = n-1 else 0 epoch_index = i >> 32 ;; epoch_length = 32 segment_key = KDF("epoch_key", [payload_key], [uint64(epoch_index)], Nk) counter = uint64((i << 1) | is_final) nonce(i) = nonce_base[0:Nn-8] || (nonce_base[Nn-8:Nn] XOR counter) (ct_i || tag_i) = AEAD.Seal(segment_key, nonce(i), "", P_i) stored_object = salt || commitment || (ct_0 || tag_0) || (ct_1 || tag_1) || ... || (ct_{n-1} || tag_{n-1}) Figure 8: SEAL-simple encryption Sullivan Expires 6 May 2027 [Page 59] Internet-Draft SEAL November 2026 Decryption recomputes the same schedule, checks the commitment to reject a wrong key early, then opens each segment. Every non-final ct_i is exactly 65536 octets and only the final ct_i may be shorter. inputs: CEK, stored_object parse salt, commitment, and ct_0 .. ct_{n-1} from stored_object recompute payload_info, payload_key, and nonce_base as above if KDF("commit", [CEK], [...payload_info, G], commitment_length) != commitment: return commitment error ;; wrong CEK, parameter set, or G for i in 0 .. n-1: recompute segment_key, nonce(i), and is_final as in encryption P_i = AEAD.Open(segment_key, nonce(i), "", ct_i || tag_i) (return a decryption error on AEAD verification failure) Figure 9: SEAL-simple decryption The reader must reject the object if segment n-1 did not open under is_final = 1. The general algorithm's snap_key derivation is not used at snap_id = 0x0000 and an implementer of this profile omits it. See Appendix D.23 for the corresponding byte-level test vector. A conforming implementation reproduces its commitment, payload_key, and nonce_base, and the per-segment nonce, ciphertext, and tag, from the fixed inputs. A.1. A Modern CFRG Suite The same instantiation admits SEAL-simple(TurboSHAKE-256, AEGIS-256), reusing existing code points (aead_id = 0x0021, kdf_id = 0x0013) with no new registrations. Sullivan Expires 6 May 2027 [Page 60] Internet-Draft SEAL November 2026 +===================+===========================================+ | Field | Value | +===================+===========================================+ | protocol_id | "SEAL-RO-v1" | +-------------------+-------------------------------------------+ | aead_id | 0x0021 (AEGIS-256) | +-------------------+-------------------------------------------+ | kdf_id | 0x0013 (TurboSHAKE-256) | +-------------------+-------------------------------------------+ | snap_id | 0x0000 (no snapshot authenticator) | +-------------------+-------------------------------------------+ | nonce_mode | 0x01 (derived) | +-------------------+-------------------------------------------+ | epoch_length | 32 | +-------------------+-------------------------------------------+ | segment_max | 65536 | +-------------------+-------------------------------------------+ | Nk | 32 | +-------------------+-------------------------------------------+ | Nn | 32 | +-------------------+-------------------------------------------+ | Nt | 16 | +-------------------+-------------------------------------------+ | Nh | 64 | +-------------------+-------------------------------------------+ | commitment_length | 64 | +-------------------+-------------------------------------------+ | layout | linear, immutable reduction (Section 4.5) | +-------------------+-------------------------------------------+ Table 16: Fixed parameters for SEAL-simple(TurboSHAKE-256, AEGIS-256) The encrypt and decrypt blocks above instantiate from this table. A 256-bit nonce under derived mode is sound because SEAL-RO-v1 never re-encrypts a position, so the non-MRAE AEAD carries no fixed-nonce reuse. The integrity budget is the AEGIS-256 row of Table 11, in place of the AES-256-GCM tag-forgery formula: about 2^83 forgery attempts in total across the object's epoch keys at a 2^-32 forgery term, independent of nonce mode. The one-step TurboSHAKE-256 derivations are exercised by the component vectors in Appendix D.15. Sullivan Expires 6 May 2027 [Page 61] Internet-Draft SEAL November 2026 Appendix B. Snapshot Authenticator Reduction SEAL's masked multiset hash is the MSet-XOR-Hash of Clarke et al. ([MSetHash]) with a deterministic mask this document adds. Each segment contributes a keyed KDF evaluation of its index and tag, the contributions XOR into an accumulator, and the published value masks that accumulator under snap_key. A different authenticator that meets the requirement of [I-D.sullivan-cfrg-raae] carries its own argument. The adversary has full read and write access to the stored segments, their metadata, the aux region, and the snapshot, and wins when SnapVerify accepts presented segments whose (index, input) set or count differs from the state the writer recorded under the presented snapshot. A segment's bytes outside its input, such as its stored nonce in random nonce mode under the masked multiset hash, are bound by its AEAD tag instead: a set that changes only those bytes passes SnapVerify and fails AEAD decryption. Whole-object rollback to an earlier honest state is excluded and is the application's freshness responsibility ([I-D.sullivan-cfrg-raae]). The game has no writer oracle. A writer's check passes on altered input with probability about 2^-(8*Nh) per check (Section 5.1). A game with a writer oracle and a win condition over the writer's intended state is deferred to [SEALPROOFS]. Publishing the accumulator in the clear would be insecure. That is the MSet-XOR-Hash with its mask removed, which is only set-collision resistant. A write adversary reads the accumulator across honest rewrites, collects the contribution differences old_contrib XOR new_contrib, and after more than 8*Nh of them solves a GF(2) system for a subset of segments it can revert without changing the accumulator. The current snapshot still verifies, so a non- historical mixture of versions is accepted with no MAC forgery. The deterministic mask defeats this attack. Here wrapped_acc = acc XOR acc_mask(n_seg, snapshot) hides the accumulator behind a one-time pad keyed by snap_key and seeded by the snapshot, the synthetic-IV derandomization of deterministic authenticated encryption ([DAE]). Two published states that collide on (n_seg, acc), or on the snapshot at equal count, would expose a raw accumulator difference. Over q_s published states that costs a birthday term q_s^2 / 2^m, with m = 8*Nh. Off that event the masks are independent one-time pads, the published transcript is independent of every accumulator, and the recombination above has no linear system left to solve. Security reduces to the PRF security of the KDF. The mask protects the accumulator only against a verifier that returns one bit, which is why neither SnapVerify nor the writer surfaces an unmasked accumulator (Section 5.1). Sullivan Expires 6 May 2027 [Page 62] Internet-Draft SEAL November 2026 A forgery is then one of two events. Either a fresh snapshot-tag input is guessed, a MAC forgery bounded by q_v / 2^m over q_v verifications, or a different segment set is made to hit a published accumulator by chance, a mix-and-match bounded by q_s * q_v / 2^m against q_s published states. The mix-and-match term is a set collision on the keyed contribution function and relies on the set- collision resistance of the MSet-XOR-Hash-style construction ([MSetHash]). The formal bound for this term is discharged in [SEALPROOFS]. Collecting the terms, the snapshot forgery advantage Adv_snap of [I-D.sullivan-cfrg-raae] is Adv_snap <= q_s^2 / 2^m + q_v / 2^m + q_s * q_v / 2^m with m = 8*Nh and dominant terms q_s^2 / 2^m and q_s * q_v / 2^m, both negligible at Nh of 32, 48, or 64. The mu-PRF cost of the snap_key derivations is not part of Adv_snap: the master bound charges it in its Level B PRF hop ([I-D.sullivan-cfrg-raae]). The argument relies on segment AEAD authenticity, already charged as Adv_muROR$ ([I-D.sullivan-cfrg-raae]), and adds no term beyond Adv_snap. That charge holds within the master bound's scope, which does not yet cover re-encrypting a position in either nonce mode. The SEAL-RW-v1 rows of Section 7 re-encrypt positions on a rewrite, and their length changes do when they revisit a pair. Per-object separation comes from the salt-bound snap_key, and the authenticator needs no assumption beyond the multi-user PRF the key schedule already uses. It does not need the commitment's collision resistance. Integrating this reduction into the combined ra-ROR proof is in preparation ([SEALPROOFS]). Appendix C. Snapshot Authenticator Design Rationale Three shapes were considered for whole-object integrity over an updatable segment set. A MAC over the concatenated tag list is the simplest, but a rewrite changes one tag in the middle of the input, so the writer recomputes over all n_seg tags on every update. A Merkle tree over the tags updates in O(log n) and can offer per- segment inclusion proofs, but it either stores interior nodes that grow with the object or re-reads segment tags to rebuild paths, and it rests on collision resistance of a hash. The masked multiset hash updates in O(1) per rewrite where the old tag needs no check or comes from trusted state, stores Nh octets of keyed snapshot plus an Nh- octet aux summary regardless of object size, needs no per-segment proofs, and needs only the multi-user PRF assumption the key schedule already carries. Its costs are the ones this document states explicitly: the accumulator stays masked, the verifier returns a single bit (Section 5.1), and the deterministic mask adds the q_s^2 birthday term to the bound (Appendix B). A profile that needs per- segment inclusion proofs or third-party verifiability needs a strict Sullivan Expires 6 May 2027 [Page 63] Internet-Draft SEAL November 2026 authenticator, such as the epoch digest tree with its public snapshot (Section 5.3.1), rather than the masked multiset hash. The masked accumulator is stored in the aux region so a writer that holds a verified snapshot from locally trusted storage or from authentication outside the construction that carries external freshness, but not the accumulator, can resume the O(1) update from it after the check of Section 5.1, rather than holding the accumulator in trusted state or re-reading every tag. Outside the two exempt cases of Section 5.1 a removed tag still needs trusted state or that re-read: under a derived nonce, except in a SEAL-AO-v1 re-mark, and once the random nonce source can repeat. Storing the accumulator is why the mask and the one-bit verifier are needed: an exposed accumulator would let a write adversary recombine observed values (Appendix B). The digest transcript verifies one segment only by recomputing over every leaf, so a reader of one segment in a large object still reads the whole leaf list. In random nonce mode the list includes Np additional octets per segment for the stored nonces. The epoch digest tree (Section 5.3) folds the leaves into per-epoch heads and the heads into the snapshot, so a reader verifies one segment from two aligned metadata reads: the target epoch's leaf run and the list of epoch heads. Each fits one segment_max read within the parameter- dependent ceiling of Table 9. That is the cost it saves the digest transcript on large content read at random. The epoch digest tree has two levels but is not a Merkle tree. Verification reconstructs an inclusion proof for a segment, recomputing its leaf, then its epoch head from the epoch's leaves, then the root from the epoch heads, rather than transmitting a compact authentication path. Recomputing an epoch head reads the epoch's leaf run as one contiguous aligned read, while a Merkle authentication path would read a logarithmic set of scattered sibling nodes. On the aligned layout, where a reader seeks in segment-sized units, one contiguous aligned read beats a scattered logarithmic path whenever the run fits one read. The same holds one level up for the list of epoch heads. The two random-access reads trade off against epoch_length. The epoch leaf run, 2^r * meta_len, grows with r and by Np octets per segment in random nonce mode, while the epoch-heads region, ceil(n_seg / 2^r) * Nh, shrinks with r as the epochs get fewer and larger. The largest r that keeps the leaf run within one aligned read therefore also makes the epoch-heads region as small as it can be, which pushes the content-size ceiling as high as it goes. At that point the two reads are each about one aligned read, so neither dominates. The rule in Section 5.3 selects that balance point. Sullivan Expires 6 May 2027 [Page 64] Internet-Draft SEAL November 2026 The list of epoch heads is n_ep * Nh octets and stays one aligned read up to a content-size ceiling: about 2 GiB at segment_max 16384 and about 128 GiB at 65536 for derived nonce mode with Nh = 32 and the recommended epoch_length. In random nonce mode the larger leaf run may require a smaller epoch_length and reduce that ceiling. The ceiling is a read-efficiency threshold, not a size limit. Past it the head list spans a few aligned reads and grows slowly, and the ceiling scales about cubically with segment_max, so a larger segment size moves it far out at the cost of coarser random-access granularity. A tree over the epoch heads would keep a single bounded read at any size but is out of scope. The two-read regime already covers the write-once content this authenticator targets. Appendix D. Test Vectors This appendix is informative. All vectors printed in this appendix use protocol_id = "SEAL-RW-v1" except Appendix D.23, Appendix D.20 and Appendix D.21, which use "SEAL-RO-v1". Every object-schedule vector uses CEK = 32 octets of 0xAA and salt = 32 octets of 0x04. The combiner vectors specify their own inputs. Each end-to-end block opens with its parameter set, grouped into three buckets: the cipher suite (aead_id, kdf_id), the geometry (segment_max), and the operational parameters (epoch_length, nonce_mode, snap_id). aead_id and kdf_id are the 2-octet IANA code points from Table 1 and Table 2. Then come the payload_info elements as encoded on the wire, the payload schedule outputs, the per-segment values, and the snapshot fields. Hexadecimal values wrap at 16 octets per line. The snapshot fields shown are the accumulator acc and the keyed snapshot snap_acc. The stored aux summary wrapped_acc = acc XOR acc_mask(n_seg, snap_acc) follows from acc and is not shown per block (Section 5.1). acc is an intermediate value, not on the wire, and the count is recovered from the segment set. Single-segment plaintexts are "Hello, SEAL!" (12 octets). Two- segment messages append "Two segments of SEAL" (20 octets) as the final segment. Most vectors use nonce_mode "random" with stored nonces repeated to the AEAD nonce length: 0x03 and 0x07 for the 12-octet suites, 0x05 and 0x09 for AEGIS-256, and 0x0b for the rewrite's new nonce. The derived-nonce vector in Appendix D.17 instead recomputes each nonce from nonce_base, and the cross-epoch vector in Appendix D.18 sets epoch_length 0. Every end-to-end vector exposes the intermediate epoch_key and segment_key per segment. Appendix D.1 gives the full KDF trace for one commitment. The other end-to-end blocks list schedule outputs only. raae-v1-vectors.json in the draft repository holds 22 entries: the eighteen end-to-end Sullivan Expires 6 May 2027 [Page 65] Internet-Draft SEAL November 2026 vectors and the SEAL-simple vector of this appendix, the negative SnapVerify vector of Appendix D.22, and two append-only vectors that this appendix does not print. Where this appendix prints an entry of that file, the values match byte for byte. The combiner vectors (Appendix D.3) exercise encode() injectivity in isolation, including the empty-sequence and same-octets-different- grouping cases, and the 38 of them are published as raae-v1-combiner- kdf-vectors.json. The AEGIS-256 blocks exercise the flat key at epoch_length = 63, the opposite endpoint to Appendix D.18. No end- to-end vector reaches the plaintext-bound nonce construction, whose component values Appendix D.19 exposes. Every printed vector uses an empty A_i except Appendix D.19.3. The SEAL-AO-v1.append.aad entry of raae-v1-vectors.json, which this appendix does not print, uses a non- empty A_i on its re-marked segment, and an empty A_i is omitted from the encoding rather than encoded as a zero-length element ([I-D.sullivan-cfrg-raae]). Appendix D.22 is a negative vector: it tampers a stored segment tag without recomputing the snapshot, and SnapVerify rejects it. D.1. Single Segment, AES-256-GCM, HKDF-SHA-256, 16384 Parameter set: cipher suite: aead_id 0x0002 (AEAD_AES_256_GCM) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 16384 operational: epoch_length 16 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 0002 segment_max_be ( 4 octets): 00004000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 10 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): 7706d3f4915f89991e9ed1028bca9a53 Sullivan Expires 6 May 2027 [Page 66] Internet-Draft SEAL November 2026 1645cc94f640b9f1078da70ff67222bd payload_key (32 octets): 936d885b565d1c766bbb953af0835933 5507c2280bf9648a1d0bd25609a862cc snap_key (32 octets): f58932df737b4ad1479b9ff869ce9851 ef3ef83c502efbd0c129eeb754cff422 Segment 0 (is_final=1): nonce (12 octets): 030303030303030303030303 epoch_key (32 octets): 0159a7acb7fdd0c98f8bf7a5e16be705 178d8ca765506f2ec1b29e3425cbfa75 segment_key (32 octets): 0159a7acb7fdd0c98f8bf7a5e16be705 178d8ca765506f2ec1b29e3425cbfa75 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000101 ciphertext (12 octets): 33b1d46e3b6ca27bbcdc193c tag (16 octets): 012cf1bcab833ab50281e4145cd1a22b contrib (32 octets): 291a662719417644d10e146ed9acd003 344f66ff0f8f4658cfed27db1d0ee8dc accumulator (32 octets): 291a662719417644d10e146ed9acd003 344f66ff0f8f4658cfed27db1d0ee8dc snap_acc (32 octets): c84ff39b53468d83982186d0d67bfe2c b2c7a83d9ebdb3bec09e0d39fee0f94a KDF trace for the commitment (HKDF-SHA-256): extract_input = encode(protocol_id, "commit", CEK): 000a5345414c2d52572d76310006636f 6d6d69740020aaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaa prk = HKDF-Extract(salt = protocol_id, extract_input): 8eb0007f1e8ac96904742b4fb4448aba 9d8a4b319f72f7e4a6d7bb8aab95737d expand_info = encode(protocol_id, "commit", ...payload_info, G, I2OSP(32, 2)): 000a5345414c2d52572d76310006636f 6d6d6974000200020004000040000002 Sullivan Expires 6 May 2027 [Page 67] Internet-Draft SEAL November 2026 00010002000100010000010000011000 20040404040404040404040404040404 04040404040404040404040404040404 04000000020020 commitment = HKDF-Expand(prk, expand_info, 32): 7706d3f4915f89991e9ed1028bca9a53 1645cc94f640b9f1078da70ff67222bd D.2. Commitment with Global Associated Data These vectors pin the G input ([I-D.sullivan-cfrg-raae]) against the schedule of Appendix D.1: the same CEK, salt, and payload_info with G at its empty default and at a nonempty value. The default case equals the Appendix D.1 commitment, since every commitment includes the G element. These values are printed here only and are not part of the end-to-end corpus. G default (empty): commitment (32 octets): 7706d3f4915f89991e9ed1028bca9a53 1645cc94f640b9f1078da70ff67222bd (the Single Segment, AES-256-GCM, HKDF-SHA-256, 16384 commitment) G = "raae-demo-g": G (11 octets): 726161652d64656d6f2d67 commitment (32 octets): 2aec8b93474daee9d26265c85a8d4349 3b9dfd23d915cf411d521abf2b5738b8 D.3. KDF Combiner Vectors These vectors exercise the KDF combiner ([I-D.sullivan-cfrg-raae]) in isolation. They demonstrate that encode() is injective over (protocol_id, label, ikm, info, L): distinct inputs, including inputs whose octets concatenate to the same string under a different grouping, have distinct framed inputs, subject to the digest- collision qualification for over-large fields in the raAE draft. KDF output pseudorandomness is a separate property; distinct framed inputs can produce colliding outputs. The full set of 38 combiner vectors, covering both KDF classes and seven of the SEAL labels, is published byte-for-byte as raae-v1-combiner-kdf-vectors.json in the draft repository. All blocks below use ikm equal to a single 32-octet element of 0xAA. Hexadecimal values wrap at 16 octets per line. Sullivan Expires 6 May 2027 [Page 68] Internet-Draft SEAL November 2026 The first pair frames the same five info octets 01 02 03 04 05 two ways. The two expand_info values differ because encode length- prefixes each element; the vector outputs also differ. The second pair shows that the empty sequence and a one-element sequence whose element is the empty octet string are distinct. The third pair repeats the distinction in the one-step form, where encode(...ikm) and encode(...info) are each a single nested element of the message. KDF.29 HKDF-SHA-256, label "commit" (info = [010203, 0405]) expand_info = encode(protocol_id, "commit", ...info, I2OSP(32, 2)): 000a5345414c2d52572d76310006636f 6d6d6974000301020300020405000200 20 output (32 octets): 0dfb8948fcc220f61f43f291648903b7 1fe6e5208647b6e18d3308f59fca0fa0 KDF.30 HKDF-SHA-256, label "commit" (info = [0102, 030405]) expand_info = encode(protocol_id, "commit", ...info, I2OSP(32, 2)): 000a5345414c2d52572d76310006636f 6d6d6974000201020003030405000200 20 output (32 octets): 299aa40869ae880bc8a064bb5afe38c4 13f420ff30bcbfac7651d5e248b3db98 KDF.8 HKDF-SHA-256, label "commit" (info = [], the empty sequence) expand_info = encode(protocol_id, "commit", ...info, I2OSP(32, 2)): 000a5345414c2d52572d76310006636f 6d6d697400020020 output (32 octets): 90073e3e9f1c855c2b7460e851f75d1a 063a1daf007f81e4a695da1a0f97fca6 KDF.28 HKDF-SHA-256, label "commit" (info = [""], one empty element) expand_info = encode(protocol_id, "commit", ...info, I2OSP(32, 2)): 000a5345414c2d52572d76310006636f 6d6d6974000000020020 output (32 octets): 5a4b4b2d59f5989c598f05a0a448acda faf27e10914c3894430ee7482c9a1913 KDF.33 TurboSHAKE-256, label "commit" (info = [010203, 0405]) encoded_input = encode(protocol_id, "commit", Sullivan Expires 6 May 2027 [Page 69] Internet-Draft SEAL November 2026 encode(...ikm), encode(...info), I2OSP(64, 2)): 000a5345414c2d52572d76310006636f 6d6d697400220020aaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaa0009000301020300 02040500020040 output (64 octets): dcefbcdced8b413e18303d2ffe1cac63 44ee71b13324caa91d1712efc6b81ca8 9ce2b62ce3aedde0ed16e14d7d17e2f2 bc69f5e856eb96f9e4845f8522b0a9b5 KDF.34 TurboSHAKE-256, label "commit" (info = [0102, 030405]) encoded_input = encode(protocol_id, "commit", encode(...ikm), encode(...info), I2OSP(64, 2)): 000a5345414c2d52572d76310006636f 6d6d697400220020aaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaa0009000201020003 03040500020040 output (64 octets): 02958e6256193c71b0cc4b8ac3273b31 15d1ac30fd9aab537ad6916206be9828 9146b56526054b3c11e22f2c375d0b24 43e8d2ad2e6a89b874aa9ef4ce2c9c77 D.4. Single Segment, AES-256-GCM, HKDF-SHA-256, 65536 Parameter set: cipher suite: aead_id 0x0002 (AEAD_AES_256_GCM) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 65536 operational: epoch_length 16 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 0002 segment_max_be ( 4 octets): 00010000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 Sullivan Expires 6 May 2027 [Page 70] Internet-Draft SEAL November 2026 epoch_length_u8 ( 1 octets): 10 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): baf545c0c2a06e25f122a9ddab2cbcdd cb023c4eef75f89768e9fcd7c7ccff48 payload_key (32 octets): 57fc9c71017c9c090bd860d19ad7d027 025449dc4f6d3c1e6f4152c9ba755407 snap_key (32 octets): 4b57952a7659282adef51db550f9d65c f736f2dbd614e5cbb1e06b08519c5322 Segment 0 (is_final=1): nonce (12 octets): 030303030303030303030303 epoch_key (32 octets): 011320f3dcde34e3e046d133ad3e26eb bed5c5eb7b5361906f250da61585b326 segment_key (32 octets): 011320f3dcde34e3e046d133ad3e26eb bed5c5eb7b5361906f250da61585b326 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000101 ciphertext (12 octets): 6235217638ab2facfdcc927e tag (16 octets): 5b525302b6b11ba202efa01c38fe8e0e contrib (32 octets): dcd8758c13c5431b5658da7e9d732b60 c76b258f2e74e084af16f06a0e62487a accumulator (32 octets): dcd8758c13c5431b5658da7e9d732b60 c76b258f2e74e084af16f06a0e62487a snap_acc (32 octets): 0135f37d803e9c8b08708d0d7f72de00 a511ba457927cf07e8d4949eb4e7e4c2 D.5. Single Segment, ChaCha20-Poly1305, HKDF-SHA-256, 16384 Sullivan Expires 6 May 2027 [Page 71] Internet-Draft SEAL November 2026 Parameter set: cipher suite: aead_id 0x001d (AEAD_CHACHA20_POLY1305) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 16384 operational: epoch_length 16 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 001d segment_max_be ( 4 octets): 00004000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 10 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): f4fe69fb8cbdd13e5350efbe556eec72 f8a15bf6b82d1cfa8d5766537798be6e payload_key (32 octets): f3cc064929b3360130fe81ada4cf0504 8ed7597e38b3f352066af1cb253a0448 snap_key (32 octets): a27457c2d4a08a79ec25137056518543 8eef13d7ccb19f568199e16b05ccc40a Segment 0 (is_final=1): nonce (12 octets): 030303030303030303030303 epoch_key (32 octets): 5ce1508680ee611d4688d906b0a177c9 5b976e445653d96a2f32eac0b5f3d647 segment_key (32 octets): 5ce1508680ee611d4688d906b0a177c9 5b976e445653d96a2f32eac0b5f3d647 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000101 ciphertext (12 octets): 380e5c6dfefe7fa9ef0c984b Sullivan Expires 6 May 2027 [Page 72] Internet-Draft SEAL November 2026 tag (16 octets): 4be0f9482fe889b5875a0abf520ecac8 contrib (32 octets): 88dc23a7bf62deece26e9701d0c45b01 7614863581f6bb061353d362fae86004 accumulator (32 octets): 88dc23a7bf62deece26e9701d0c45b01 7614863581f6bb061353d362fae86004 snap_acc (32 octets): 66958e1b459d872ee1cb648f38998faf 822954c178d8a5b45866266e33abf563 D.6. Single Segment, ChaCha20-Poly1305, HKDF-SHA-256, 65536 Parameter set: cipher suite: aead_id 0x001d (AEAD_CHACHA20_POLY1305) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 65536 operational: epoch_length 16 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 001d segment_max_be ( 4 octets): 00010000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 10 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): 4c53947f6a3009af80b4d460aedbeeb4 3f0aa693ae4404077f2c36a627af857c payload_key (32 octets): a85bf6fc24a865aa90d27793e271ba5e 083e8e8a870b4cc6b3895ee826568593 snap_key (32 octets): 233fc79182c09d21e15018bb857344ba 184e95ad759e7adac62e27abea9f1dba Sullivan Expires 6 May 2027 [Page 73] Internet-Draft SEAL November 2026 Segment 0 (is_final=1): nonce (12 octets): 030303030303030303030303 epoch_key (32 octets): daad450b03a10156d0c852a4c67f50dc 6bf2530c162c41ea65ea9940d4dcc4a1 segment_key (32 octets): daad450b03a10156d0c852a4c67f50dc 6bf2530c162c41ea65ea9940d4dcc4a1 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000101 ciphertext (12 octets): 2a1515bbd956c85130f6b4db tag (16 octets): e1bd87be8732e6896cc5d9d14ffcbcf3 contrib (32 octets): 4ea365498faa3849cc569821e808f592 558a322fb5657722d6f614bcedffd5d2 accumulator (32 octets): 4ea365498faa3849cc569821e808f592 558a322fb5657722d6f614bcedffd5d2 snap_acc (32 octets): 948b744ce3566f8275d37c5c5c23e16d 0b96a8c1ebf02ee2e42c88d68c31f653 D.7. Single Segment, AEGIS-256, HKDF-SHA-256, 16384 Parameter set: cipher suite: aead_id 0x0021 (AEAD_AEGIS256) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 16384 operational: epoch_length 63 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 0021 segment_max_be ( 4 octets): 00004000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 3f Sullivan Expires 6 May 2027 [Page 74] Internet-Draft SEAL November 2026 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): e68d98a673d4efa934648ebd5becd3a2 9dc693ca67bd5045c16a98d7c43904fc payload_key (32 octets): 81ef074bf215dfc0762bf55b46e175f5 77c9a172b55cd72a3867f8c47926f4d9 snap_key (32 octets): 9cbe04d362b6e8b1a681a0454e9047e3 fa4b043da79e5c26fbcfba4121e042e4 Segment 0 (is_final=1): nonce (32 octets): 05050505050505050505050505050505 05050505050505050505050505050505 epoch_key (32 octets): 86c56736585c2d3b2c94f8cc43bd1a9a cead3e50488662d76ac8d2b35378a9d8 segment_key (32 octets): 86c56736585c2d3b2c94f8cc43bd1a9a cead3e50488662d76ac8d2b35378a9d8 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000101 ciphertext (12 octets): 634d88305091db5f0098e812 tag (16 octets): ea991eed529172733e08586177bf9a7e contrib (32 octets): d353493b40ae4e26caf474cbe932da0e b88793b6b59e5c4376a2ea74bcc2acd8 accumulator (32 octets): d353493b40ae4e26caf474cbe932da0e b88793b6b59e5c4376a2ea74bcc2acd8 snap_acc (32 octets): 43f0bcf35f24090ce02a13ee87d2d3df d1870b7327081864269a2af64113803e D.8. Single Segment, AEGIS-256, HKDF-SHA-256, 65536 Sullivan Expires 6 May 2027 [Page 75] Internet-Draft SEAL November 2026 Parameter set: cipher suite: aead_id 0x0021 (AEAD_AEGIS256) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 65536 operational: epoch_length 63 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 0021 segment_max_be ( 4 octets): 00010000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 3f salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): cf3c988d6cfc47d936e88f85cf97b623 c106bc9500baa1fd14fc4f7e1362352c payload_key (32 octets): 5a60091f7fe09a7a8e282354c0bfa9f9 01406d4a47df0744f9b45ecd66e708be snap_key (32 octets): e40f2739a89376e9548956e08fb79c55 5b1f958068fa482a12c0eef725b8d16c Segment 0 (is_final=1): nonce (32 octets): 05050505050505050505050505050505 05050505050505050505050505050505 epoch_key (32 octets): f15f37e9c4e2565cfe8ee1cc27332090 e4fd3a88948a2fd246cc4cb717c9a05b segment_key (32 octets): f15f37e9c4e2565cfe8ee1cc27332090 e4fd3a88948a2fd246cc4cb717c9a05b segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000101 ciphertext (12 octets): Sullivan Expires 6 May 2027 [Page 76] Internet-Draft SEAL November 2026 75a66ad40b2f7dbea1d72669 tag (16 octets): a0ff8815a35577d0ed0d5b1483f89131 contrib (32 octets): 7450c8653dc69f6d0601c1cf6f14abff 4254241d31ddd1200ecd9229ecd9ded2 accumulator (32 octets): 7450c8653dc69f6d0601c1cf6f14abff 4254241d31ddd1200ecd9229ecd9ded2 snap_acc (32 octets): 8ac245e01ffded4c18ad1e45b2a0e652 307d4704714c0fe59636eb2ea7dd867f D.9. Two Segment, AES-256-GCM, HKDF-SHA-256, 16384 Parameter set: cipher suite: aead_id 0x0002 (AEAD_AES_256_GCM) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 16384 operational: epoch_length 16 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 0002 segment_max_be ( 4 octets): 00004000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 10 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): 7706d3f4915f89991e9ed1028bca9a53 1645cc94f640b9f1078da70ff67222bd payload_key (32 octets): 936d885b565d1c766bbb953af0835933 5507c2280bf9648a1d0bd25609a862cc snap_key (32 octets): f58932df737b4ad1479b9ff869ce9851 Sullivan Expires 6 May 2027 [Page 77] Internet-Draft SEAL November 2026 ef3ef83c502efbd0c129eeb754cff422 Segment 0 (is_final=0): nonce (12 octets): 030303030303030303030303 epoch_key (32 octets): 0159a7acb7fdd0c98f8bf7a5e16be705 178d8ca765506f2ec1b29e3425cbfa75 segment_key (32 octets): 0159a7acb7fdd0c98f8bf7a5e16be705 178d8ca765506f2ec1b29e3425cbfa75 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000100 ciphertext (12 octets): 33b1d46e3b6ca27bbcdc193c tag (16 octets): ceef44c0a1e66b099ca426bd45ab6ce1 contrib (32 octets): d57594c97679e40db9a87de7f5b92bc8 6b2b605e1fb99dc68881ffb41daa47e1 Segment 1 (is_final=1): nonce (12 octets): 070707070707070707070707 epoch_key (32 octets): 0159a7acb7fdd0c98f8bf7a5e16be705 178d8ca765506f2ec1b29e3425cbfa75 segment_key (32 octets): 0159a7acb7fdd0c98f8bf7a5e16be705 178d8ca765506f2ec1b29e3425cbfa75 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000001000101 ciphertext (20 octets): 1e1199855c2bf0c998c4e6be6d2411b3 cd57ec92 tag (16 octets): 9a42952022f491d13ce63ca64ae03d35 contrib (32 octets): c16e615b698818e59627ff16b4c3d8bf 21e10981587f463acbc2387f1e47b425 accumulator (32 octets): 141bf5921ff1fce82f8f82f1417af377 4aca69df47c6dbfc4343c7cb03edf3c4 snap_acc (32 octets): 69ab7af3f074d9deaa75ca0192d72a95 e0d1b34a045cc716439a9a0cceb5b781 Sullivan Expires 6 May 2027 [Page 78] Internet-Draft SEAL November 2026 D.10. Two Segment, AES-256-GCM, HKDF-SHA-256, 65536 Parameter set: cipher suite: aead_id 0x0002 (AEAD_AES_256_GCM) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 65536 operational: epoch_length 16 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 0002 segment_max_be ( 4 octets): 00010000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 10 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): baf545c0c2a06e25f122a9ddab2cbcdd cb023c4eef75f89768e9fcd7c7ccff48 payload_key (32 octets): 57fc9c71017c9c090bd860d19ad7d027 025449dc4f6d3c1e6f4152c9ba755407 snap_key (32 octets): 4b57952a7659282adef51db550f9d65c f736f2dbd614e5cbb1e06b08519c5322 Segment 0 (is_final=0): nonce (12 octets): 030303030303030303030303 epoch_key (32 octets): 011320f3dcde34e3e046d133ad3e26eb bed5c5eb7b5361906f250da61585b326 segment_key (32 octets): 011320f3dcde34e3e046d133ad3e26eb bed5c5eb7b5361906f250da61585b326 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000100 Sullivan Expires 6 May 2027 [Page 79] Internet-Draft SEAL November 2026 ciphertext (12 octets): 6235217638ab2facfdcc927e tag (16 octets): b2297c8e21d2f47eb4ec84160661f457 contrib (32 octets): ae041a29353fc08425fe898e9f5aa2ef 5176b5e33c5159dbffca108f1ed2b4d5 Segment 1 (is_final=1): nonce (12 octets): 070707070707070707070707 epoch_key (32 octets): 011320f3dcde34e3e046d133ad3e26eb bed5c5eb7b5361906f250da61585b326 segment_key (32 octets): 011320f3dcde34e3e046d133ad3e26eb bed5c5eb7b5361906f250da61585b326 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000001000101 ciphertext (20 octets): 8bbebac4b9af2bc7f1b6c0d611a8bc61 e060785f tag (16 octets): 683ca39408012058e29496e046765c50 contrib (32 octets): ce1de64f2686b4639b99880adb98630c 85cd1486f02b1e2b6e71908d770f0033 accumulator (32 octets): 6019fc6613b974e7be67018444c2c1e3 d4bba165cc7a47f091bb800269ddb4e6 snap_acc (32 octets): cc155a59891885838c1667f916f50d89 c45759f84b7e5dbd1159e653e08842ef D.11. Two Segment, ChaCha20-Poly1305, HKDF-SHA-256, 16384 Parameter set: cipher suite: aead_id 0x001d (AEAD_CHACHA20_POLY1305) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 16384 operational: epoch_length 16 nonce_mode random snap_id 0x0001 (masked multiset hash) Sullivan Expires 6 May 2027 [Page 80] Internet-Draft SEAL November 2026 payload_info (the KDF frames each element): aead_id ( 2 octets): 001d segment_max_be ( 4 octets): 00004000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 10 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): f4fe69fb8cbdd13e5350efbe556eec72 f8a15bf6b82d1cfa8d5766537798be6e payload_key (32 octets): f3cc064929b3360130fe81ada4cf0504 8ed7597e38b3f352066af1cb253a0448 snap_key (32 octets): a27457c2d4a08a79ec25137056518543 8eef13d7ccb19f568199e16b05ccc40a Segment 0 (is_final=0): nonce (12 octets): 030303030303030303030303 epoch_key (32 octets): 5ce1508680ee611d4688d906b0a177c9 5b976e445653d96a2f32eac0b5f3d647 segment_key (32 octets): 5ce1508680ee611d4688d906b0a177c9 5b976e445653d96a2f32eac0b5f3d647 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000100 ciphertext (12 octets): 380e5c6dfefe7fa9ef0c984b tag (16 octets): 3b0ca59d3fad576db397443169f530d7 contrib (32 octets): a2dbc5eab4c796ea413a170faa145efa a437e6f3614853aa03c25f1d71fff29b Segment 1 (is_final=1): nonce (12 octets): 070707070707070707070707 epoch_key (32 octets): 5ce1508680ee611d4688d906b0a177c9 5b976e445653d96a2f32eac0b5f3d647 Sullivan Expires 6 May 2027 [Page 81] Internet-Draft SEAL November 2026 segment_key (32 octets): 5ce1508680ee611d4688d906b0a177c9 5b976e445653d96a2f32eac0b5f3d647 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000001000101 ciphertext (20 octets): 01009b303595704e4ca46e044a55b085 bbcbade7 tag (16 octets): 9a72e1e7662150a1da1d67e2cdaffdea contrib (32 octets): 23c59a50f5f8921707f067a0222a8bab aeea7fe7b60c2bf5be7462f59fd3c0ba accumulator (32 octets): 811e5fba413f04fd46ca70af883ed551 0add9914d744785fbdb63de8ee2c3221 snap_acc (32 octets): 6de876d299401a9b6bf938349ad63972 2608fc82a226c994e7783228a14cb75a D.12. Two Segment, ChaCha20-Poly1305, HKDF-SHA-256, 65536 Parameter set: cipher suite: aead_id 0x001d (AEAD_CHACHA20_POLY1305) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 65536 operational: epoch_length 16 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 001d segment_max_be ( 4 octets): 00010000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 10 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: Sullivan Expires 6 May 2027 [Page 82] Internet-Draft SEAL November 2026 commitment (32 octets): 4c53947f6a3009af80b4d460aedbeeb4 3f0aa693ae4404077f2c36a627af857c payload_key (32 octets): a85bf6fc24a865aa90d27793e271ba5e 083e8e8a870b4cc6b3895ee826568593 snap_key (32 octets): 233fc79182c09d21e15018bb857344ba 184e95ad759e7adac62e27abea9f1dba Segment 0 (is_final=0): nonce (12 octets): 030303030303030303030303 epoch_key (32 octets): daad450b03a10156d0c852a4c67f50dc 6bf2530c162c41ea65ea9940d4dcc4a1 segment_key (32 octets): daad450b03a10156d0c852a4c67f50dc 6bf2530c162c41ea65ea9940d4dcc4a1 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000100 ciphertext (12 octets): 2a1515bbd956c85130f6b4db tag (16 octets): 3477dc5bc923430f576d8817ec6b00ee contrib (32 octets): c94ae2e9364ca8b045a8b7aac15fe6fc 4d5b85b5d4014664a62e6af0900bfb5d Segment 1 (is_final=1): nonce (12 octets): 070707070707070707070707 epoch_key (32 octets): daad450b03a10156d0c852a4c67f50dc 6bf2530c162c41ea65ea9940d4dcc4a1 segment_key (32 octets): daad450b03a10156d0c852a4c67f50dc 6bf2530c162c41ea65ea9940d4dcc4a1 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000001000101 ciphertext (20 octets): 52a3232463a12502753a2594adfdc5a7 ae7da02e tag (16 octets): f4b4fb238f6fb2655cc7f296806b877a contrib (32 octets): 07f76273b413de6e98241836cb9b1a14 Sullivan Expires 6 May 2027 [Page 83] Internet-Draft SEAL November 2026 9852c4fa47436a2adeb44f40023c4e01 accumulator (32 octets): cebd809a825f76dedd8caf9c0ac4fce8 d509414f93422c4e789a25b09237b55c snap_acc (32 octets): 7b448954d1af6e545b0dfba7ea33b1a7 e9709331559b9ab8fd4ac8a9a912953a D.13. Two Segment, AEGIS-256, HKDF-SHA-256, 16384 Parameter set: cipher suite: aead_id 0x0021 (AEAD_AEGIS256) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 16384 operational: epoch_length 63 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 0021 segment_max_be ( 4 octets): 00004000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 3f salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): e68d98a673d4efa934648ebd5becd3a2 9dc693ca67bd5045c16a98d7c43904fc payload_key (32 octets): 81ef074bf215dfc0762bf55b46e175f5 77c9a172b55cd72a3867f8c47926f4d9 snap_key (32 octets): 9cbe04d362b6e8b1a681a0454e9047e3 fa4b043da79e5c26fbcfba4121e042e4 Segment 0 (is_final=0): nonce (32 octets): 05050505050505050505050505050505 Sullivan Expires 6 May 2027 [Page 84] Internet-Draft SEAL November 2026 05050505050505050505050505050505 epoch_key (32 octets): 86c56736585c2d3b2c94f8cc43bd1a9a cead3e50488662d76ac8d2b35378a9d8 segment_key (32 octets): 86c56736585c2d3b2c94f8cc43bd1a9a cead3e50488662d76ac8d2b35378a9d8 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000100 ciphertext (12 octets): 634d88305091db5f0098e812 tag (16 octets): fd26193262bd6456169534b3f308fe33 contrib (32 octets): edeeece9b0520ab3e7cda65bc3a5b2e8 0ca5094a0a4e79b1f540a3c55745419b Segment 1 (is_final=1): nonce (32 octets): 09090909090909090909090909090909 09090909090909090909090909090909 epoch_key (32 octets): 86c56736585c2d3b2c94f8cc43bd1a9a cead3e50488662d76ac8d2b35378a9d8 segment_key (32 octets): 86c56736585c2d3b2c94f8cc43bd1a9a cead3e50488662d76ac8d2b35378a9d8 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000001000101 ciphertext (20 octets): 913ab481d7c9073abb785082ec4157f9 d905694a tag (16 octets): bc42b159ace2c7881763a7d1fa211cd4 contrib (32 octets): e53f056d7c40d763b975fd277a3a50bb 20d6a58f7eac54fd76fd425daf15b803 accumulator (32 octets): 08d1e984cc12ddd05eb85b7cb99fe253 2c73acc574e22d4c83bde198f850f998 snap_acc (32 octets): 187930ddac437a7a0e174dd45e1d76ed e2fd180113da1432618745d6088c825e D.14. Two Segment, AEGIS-256, HKDF-SHA-256, 65536 Sullivan Expires 6 May 2027 [Page 85] Internet-Draft SEAL November 2026 Parameter set: cipher suite: aead_id 0x0021 (AEAD_AEGIS256) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 65536 operational: epoch_length 63 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 0021 segment_max_be ( 4 octets): 00010000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 3f salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): cf3c988d6cfc47d936e88f85cf97b623 c106bc9500baa1fd14fc4f7e1362352c payload_key (32 octets): 5a60091f7fe09a7a8e282354c0bfa9f9 01406d4a47df0744f9b45ecd66e708be snap_key (32 octets): e40f2739a89376e9548956e08fb79c55 5b1f958068fa482a12c0eef725b8d16c Segment 0 (is_final=0): nonce (32 octets): 05050505050505050505050505050505 05050505050505050505050505050505 epoch_key (32 octets): f15f37e9c4e2565cfe8ee1cc27332090 e4fd3a88948a2fd246cc4cb717c9a05b segment_key (32 octets): f15f37e9c4e2565cfe8ee1cc27332090 e4fd3a88948a2fd246cc4cb717c9a05b segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000100 ciphertext (12 octets): Sullivan Expires 6 May 2027 [Page 86] Internet-Draft SEAL November 2026 75a66ad40b2f7dbea1d72669 tag (16 octets): 2527a6188e428150cbdb481fa7e2cc6d contrib (32 octets): 0ed1c179ee02b2cdecdf58866f65425d 4f8a8a07ec19de9037dcdf883cebd187 Segment 1 (is_final=1): nonce (32 octets): 09090909090909090909090909090909 09090909090909090909090909090909 epoch_key (32 octets): f15f37e9c4e2565cfe8ee1cc27332090 e4fd3a88948a2fd246cc4cb717c9a05b segment_key (32 octets): f15f37e9c4e2565cfe8ee1cc27332090 e4fd3a88948a2fd246cc4cb717c9a05b segment_aad (24 octets): 00095345414c2d444154410008000000 0000000001000101 ciphertext (20 octets): 8aaf9bbe14960ee32ee5f07842a22cab 5e5841d3 tag (16 octets): 5d5555c9a75f17e4a0afbd87de309f92 contrib (32 octets): 674340ba2fca04bbf17816f1f90e5068 d150cb66f948e5f9e396bf63e958ff25 accumulator (32 octets): 699281c3c1c8b6761da74e77966b1235 9eda416115513b69d44a60ebd5b32ea2 snap_acc (32 octets): 7e2f4e402e53f87f86b27b3ae9d1e1e5 3295170b97270e7fecf91054601cbdd8 D.15. TurboSHAKE-256 Cipher Suite Vectors These two vectors exercise the one-step TurboSHAKE-256 cipher suite (Table 2), for which Nh = 64. The commitment, snap_key, contrib, the accumulator, and snapshot are therefore 64 octets, while payload_key (Nk) and the nonces (Nn) are unchanged. D.15.1. Single Segment, AES-256-GCM, TurboSHAKE-256, 65536 Sullivan Expires 6 May 2027 [Page 87] Internet-Draft SEAL November 2026 Parameter set: cipher suite: aead_id 0x0002 (AEAD_AES_256_GCM) kdf_id 0x0013 (TurboSHAKE-256) geometry: segment_max 65536 operational: epoch_length 16 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 0002 segment_max_be ( 4 octets): 00010000 kdf_id ( 2 octets): 0013 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 10 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (64 octets): 74b34698fda554d1805e30d33cf5cb18 04f95059e28c64fe11b41f3a1f355701 7e8c744a3708f47db962bf9233222a77 ff9a191df16419ff722aca29fa9d2f14 payload_key (32 octets): 5419c913980f56ddcfd4a2590c5d5bfb 33968ff3bfefad6d31fe9b77d7a9afab snap_key (64 octets): 3bf743f5c345b123b2b9ef8465e10e0c cb341f4f9e20032ae253366d0342d4d6 ecdad10e213abd4f642a869e257f1086 d36209e4ac689515ed43fab04468ddd3 Segment 0 (is_final=1): nonce (12 octets): 030303030303030303030303 epoch_key (32 octets): a474c2308f069c388aa3cb9d8e5d5524 6ed5ca18a10158743394574ace0c714f segment_key (32 octets): a474c2308f069c388aa3cb9d8e5d5524 6ed5ca18a10158743394574ace0c714f segment_aad (24 octets): Sullivan Expires 6 May 2027 [Page 88] Internet-Draft SEAL November 2026 00095345414c2d444154410008000000 0000000000000101 ciphertext (12 octets): 4067fdd6e1ddd8ff059ae3fd tag (16 octets): 4802fc86296bc474a083c1cb0d9f9adf contrib (64 octets): 13f7e229f49cdf0d6a360f22d9fbc6ce 1277e62bcc064e23049b941d3334762b 2709ee38a9f4234503652ebb5868c00d 7c1dc1ba2d946643dc4b3a77371262e7 accumulator (64 octets): 13f7e229f49cdf0d6a360f22d9fbc6ce 1277e62bcc064e23049b941d3334762b 2709ee38a9f4234503652ebb5868c00d 7c1dc1ba2d946643dc4b3a77371262e7 snap_acc (64 octets): cba95a3f405e508897eb53d66ded2861 6ad9ea56f10e2ef9098f8b4c6a4ed0e0 6eb60619f0e3a6526977e33c94688b2f 838a0e5ce51ec8d5fbdd14db62a1c1d1 D.15.2. Two Segment, AES-256-GCM, TurboSHAKE-256, 65536 Parameter set: cipher suite: aead_id 0x0002 (AEAD_AES_256_GCM) kdf_id 0x0013 (TurboSHAKE-256) geometry: segment_max 65536 operational: epoch_length 16 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 0002 segment_max_be ( 4 octets): 00010000 kdf_id ( 2 octets): 0013 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 10 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Sullivan Expires 6 May 2027 [Page 89] Internet-Draft SEAL November 2026 Payload schedule: commitment (64 octets): 74b34698fda554d1805e30d33cf5cb18 04f95059e28c64fe11b41f3a1f355701 7e8c744a3708f47db962bf9233222a77 ff9a191df16419ff722aca29fa9d2f14 payload_key (32 octets): 5419c913980f56ddcfd4a2590c5d5bfb 33968ff3bfefad6d31fe9b77d7a9afab snap_key (64 octets): 3bf743f5c345b123b2b9ef8465e10e0c cb341f4f9e20032ae253366d0342d4d6 ecdad10e213abd4f642a869e257f1086 d36209e4ac689515ed43fab04468ddd3 Segment 0 (is_final=0): nonce (12 octets): 030303030303030303030303 epoch_key (32 octets): a474c2308f069c388aa3cb9d8e5d5524 6ed5ca18a10158743394574ace0c714f segment_key (32 octets): a474c2308f069c388aa3cb9d8e5d5524 6ed5ca18a10158743394574ace0c714f segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000100 ciphertext (12 octets): 4067fdd6e1ddd8ff059ae3fd tag (16 octets): 9ec2df633d9b06520da843775f0e022b contrib (64 octets): 98f0a32bafce3a6535b1550ddec451d9 fb241f8c18f9439cbf3f55a54d68c432 882afebcc0d05f6a75c899539d4475c8 80dfff6efe858c2c0caaed00097f0929 Segment 1 (is_final=1): nonce (12 octets): 070707070707070707070707 epoch_key (32 octets): a474c2308f069c388aa3cb9d8e5d5524 6ed5ca18a10158743394574ace0c714f segment_key (32 octets): a474c2308f069c388aa3cb9d8e5d5524 6ed5ca18a10158743394574ace0c714f segment_aad (24 octets): 00095345414c2d444154410008000000 0000000001000101 Sullivan Expires 6 May 2027 [Page 90] Internet-Draft SEAL November 2026 ciphertext (20 octets): de8a0c958ef95b9718782a249d08f205 cbe2746f tag (16 octets): c8381466c731aebfeb64a51ad11e783c contrib (64 octets): 1e9f17eb45852e1558bf726f801f843c dd08c999442c1286097ceee3412881a7 669886b99722fef82186c77c9f44861e 747ce25a6b6dd5be69dbcb0db8b61e01 accumulator (64 octets): 866fb4c0ea4b14706d0e27625edbd5e5 262cd6155cd5511ab643bb460c404595 eeb2780557f2a192544e5e2f0200f3d6 f4a31d3495e859926571260db1c91728 snap_acc (64 octets): 10fd26558a9366f95105e11a14e71c11 7e51b1518aa162a62af17587a56171c7 bbd707c61b0d4012e6d1f3d529039256 268b3cec9491bbd37bbbc88a0eff8e2c D.16. Segment Rewrite Vector This vector applies RewriteSegment ([I-D.sullivan-cfrg-raae]) to segment 0 of a two-segment AES-256-GCM message, replacing its plaintext under a fresh nonce. The replacement plaintext is the 22-octet ASCII string "Rewritten SEAL segment". acc_delta = old_contrib XOR new_contrib, the new accumulator is the old accumulator XOR acc_delta, and snapshot is recomputed over the count and the new accumulator. D.16.1. Two Segment, AES-256-GCM, HKDF-SHA-256, 65536 (Rewrite) Parameter set: cipher suite: aead_id 0x0002 (AEAD_AES_256_GCM) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 65536 operational: epoch_length 16 nonce_mode random snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 0002 segment_max_be ( 4 octets): 00010000 Sullivan Expires 6 May 2027 [Page 91] Internet-Draft SEAL November 2026 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 10 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): baf545c0c2a06e25f122a9ddab2cbcdd cb023c4eef75f89768e9fcd7c7ccff48 payload_key (32 octets): 57fc9c71017c9c090bd860d19ad7d027 025449dc4f6d3c1e6f4152c9ba755407 snap_key (32 octets): 4b57952a7659282adef51db550f9d65c f736f2dbd614e5cbb1e06b08519c5322 Segment 0 (is_final=0): nonce (12 octets): 030303030303030303030303 epoch_key (32 octets): 011320f3dcde34e3e046d133ad3e26eb bed5c5eb7b5361906f250da61585b326 segment_key (32 octets): 011320f3dcde34e3e046d133ad3e26eb bed5c5eb7b5361906f250da61585b326 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000100 ciphertext (12 octets): 6235217638ab2facfdcc927e tag (16 octets): b2297c8e21d2f47eb4ec84160661f457 contrib (32 octets): ae041a29353fc08425fe898e9f5aa2ef 5176b5e33c5159dbffca108f1ed2b4d5 Segment 1 (is_final=1): nonce (12 octets): 070707070707070707070707 epoch_key (32 octets): 011320f3dcde34e3e046d133ad3e26eb bed5c5eb7b5361906f250da61585b326 segment_key (32 octets): 011320f3dcde34e3e046d133ad3e26eb bed5c5eb7b5361906f250da61585b326 Sullivan Expires 6 May 2027 [Page 92] Internet-Draft SEAL November 2026 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000001000101 ciphertext (20 octets): 8bbebac4b9af2bc7f1b6c0d611a8bc61 e060785f tag (16 octets): 683ca39408012058e29496e046765c50 contrib (32 octets): ce1de64f2686b4639b99880adb98630c 85cd1486f02b1e2b6e71908d770f0033 accumulator (32 octets): 6019fc6613b974e7be67018444c2c1e3 d4bba165cc7a47f091bb800269ddb4e6 snap_acc (32 octets): cc155a59891885838c1667f916f50d89 c45759f84b7e5dbd1159e653e08842ef Rewrite of segment 0: new_plaintext (22 octets): 52657772697474656e205345414c2073 65676d656e74 new_nonce (12 octets): 0b0b0b0b0b0b0b0b0b0b0b0b new_ciphertext (22 octets): f139f80e303a51953d7718d046dfb81e be46e67be213 new_tag (16 octets): c57af8e6e9dff90f7498f8c2c6144ba2 old_contrib (32 octets): ae041a29353fc08425fe898e9f5aa2ef 5176b5e33c5159dbffca108f1ed2b4d5 new_contrib (32 octets): 4de295ebaf141361efd866aa850b9f4b 3c95926d3fa3f3c9325c6ccd28d959c9 acc_delta (32 octets): e3e68fc29a2bd3e5ca26ef241a513da4 6de3278e03f2aa12cd967c42360bed1c new_accumulator (32 octets): 83ff73a48992a7027441eea05e93fc47 b95886ebcf88ede25c2dfc405fd659fa new_snap_acc (32 octets): 5fd658c6d95a2ee4a81446eecd62c313 394500bcd0d440ac360df2b1e3562f6e Sullivan Expires 6 May 2027 [Page 93] Internet-Draft SEAL November 2026 D.17. Derived-Nonce Cipher Suite Vector This vector exercises AES-256-GCM-SIV (Table 1), the MRAE cipher suite that uses derived nonce mode. There is no stored per-segment nonce: each nonce is recomputed from nonce_base by the formula in [I-D.sullivan-cfrg-raae], which XORs uint64((i << 1) | is_final), the segment index and finality bit, into the low 8 octets of nonce_base (here Nn = 12). The block applies RewriteSegment to segment 0. The replacement plaintext is the 22-octet ASCII string "Rewritten SEAL segment". Because the nonce is recomputed deterministically, the rewrite reuses the same nonce as the original segment 0. AES-256- GCM-SIV is misuse-resistant, so the reuse leaks only equality of identical plaintext-and-context pairs, not plaintext. D.17.1. Two Segment, AES-256-GCM-SIV, HKDF-SHA-256, 65536 (Rewrite) Parameter set: cipher suite: aead_id 0x001f (AEAD_AES_256_GCM_SIV) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 65536 operational: epoch_length 0 nonce_mode derived snap_id 0x0001 (masked multiset hash) payload_info (the KDF frames each element): aead_id ( 2 octets): 001f segment_max_be ( 4 octets): 00010000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 01 epoch_length_u8 ( 1 octets): 00 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): 82574d5980c62d4a331ef006e2e2f081 be81d9dc32be60be6224a5efdc78805e payload_key (32 octets): 7c38a1f14a04024e9d9d2aad57e01422 a3284d4489ab8eaa3b84a7829b5216a4 snap_key (32 octets): 19eae9f0849b5f1849d8a36dd6c0bf9c Sullivan Expires 6 May 2027 [Page 94] Internet-Draft SEAL November 2026 319f94b653f4b3ac1e7133fa8db64ef4 nonce_base (12 octets): d0d4562a7249b035821acd2f Segment 0 (is_final=0): nonce (12 octets): (derived from nonce_base, not stored) d0d4562a7249b035821acd2f epoch_key (32 octets): 0727cdd2fd3b3bbbfb90eb4a7233acf7 c4bac5fbaa11029c96e4c40023d71ca5 segment_key (32 octets): 0727cdd2fd3b3bbbfb90eb4a7233acf7 c4bac5fbaa11029c96e4c40023d71ca5 segment_aad ( 0 octets): (empty) ciphertext (12 octets): 76331d2cb099dbb6fd3b3684 tag (16 octets): b13c9eb8f92686a4e19c66146418ab45 contrib (32 octets): 86c55cbb773f15358770f5e05928d0d5 eca971066ac7fa933ade7d67860dde2a Segment 1 (is_final=1): nonce (12 octets): (derived from nonce_base, not stored) d0d4562a7249b035821acd2c epoch_key (32 octets): 67ee8f3ea6bbb4a1878921e0ea5cfdec 032adf44a243da620e3845c53add5d73 segment_key (32 octets): 67ee8f3ea6bbb4a1878921e0ea5cfdec 032adf44a243da620e3845c53add5d73 segment_aad ( 0 octets): (empty) ciphertext (20 octets): 2d4a7e360bbcf0d36d2922f2d3e90637 771e2ea0 tag (16 octets): df7bbecbbb144bca9fbcb4a41923f18e contrib (32 octets): 2f0b945691522f24d96ab65cbaaa0682 7cc9fbad49571946d3b6491a6ee2e981 accumulator (32 octets): a9cec8ede66d3a115e1a43bce382d657 90608aab2390e3d5e968347de8ef37ab snap_acc (32 octets): c9ac169402f3121c28b7b90ef4435a98 7582858b2145bc88cd4ac4a090cbd52e Rewrite of segment 0: Sullivan Expires 6 May 2027 [Page 95] Internet-Draft SEAL November 2026 new_plaintext (22 octets): 52657772697474656e205345414c2073 65676d656e74 new_nonce (12 octets): d0d4562a7249b035821acd2f new_ciphertext (22 octets): 95e420d047cbf2d4f2e61cf7c49a125c a34eac782914 new_tag (16 octets): 933909c1f14be99ed92db7f4c4c2d378 old_contrib (32 octets): 86c55cbb773f15358770f5e05928d0d5 eca971066ac7fa933ade7d67860dde2a new_contrib (32 octets): af24901123a931742d0d8a93641aba65 d8496745f529b055807e2f9eb8ba38f7 acc_delta (32 octets): 29e1ccaa54962441aa7d7f733d326ab0 34e016439fee4ac6baa052f93eb7e6dd new_accumulator (32 octets): 802f0447b2fb1e50f4673ccfdeb0bce7 a4809ce8bc7ea91353c86684d658d176 new_snap_acc (32 octets): 1c2de652b504fffa4fec0394aa119952 58744dccfea4127612eaa120c6098a0f D.18. Cross-Epoch Key Vector This vector sets epoch_length = 0, the finest epoch partition, so the epoch index equals the segment index and each segment is sealed under a distinct epoch key ([I-D.sullivan-cfrg-raae]). The block exposes the intermediate epoch_key(i) and segment_key(i) for each segment. At epoch_length = 0 the shift is the identity, so segment_key(i) equals epoch_key(i), and epoch_key(0) and epoch_key(1) differ. D.18.1. Two Segment, AES-256-GCM, HKDF-SHA-256, 65536, epoch_length 0 Parameter set: cipher suite: aead_id 0x0002 (AEAD_AES_256_GCM) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 65536 operational: epoch_length 0 nonce_mode random snap_id 0x0001 (masked multiset hash) Sullivan Expires 6 May 2027 [Page 96] Internet-Draft SEAL November 2026 payload_info (the KDF frames each element): aead_id ( 2 octets): 0002 segment_max_be ( 4 octets): 00010000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0001 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 00 epoch_length_u8 ( 1 octets): 00 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): 6681ec9a2ad3b8ce46dca3d54de08c36 2c5d9bb6b0c9d45a53cdd98c032b54f6 payload_key (32 octets): f9a1bb3ab8ebbd80c287e14c072ad419 45a2b2bf135359e627f64dc747db87c4 snap_key (32 octets): ffdbbcc79855be4da0d7d91eb7ba1150 5465163b9193623428b83c8945ee8bcd Segment 0 (is_final=0): nonce (12 octets): 030303030303030303030303 epoch_key (32 octets): 511dc5e746e42c6722a9d52c387f9bb3 aabb918d6ce008bd0109d158816efc22 segment_key (32 octets): 511dc5e746e42c6722a9d52c387f9bb3 aabb918d6ce008bd0109d158816efc22 segment_aad (24 octets): 00095345414c2d444154410008000000 0000000000000100 ciphertext (12 octets): 427d05a1e9451ab4e471f75e tag (16 octets): 32aea9a3939302dfbb0750474c21cafd contrib (32 octets): a0853446684127b7220f5db5493d47ba 205f71948502f47a7cfd59f6c15fc648 Segment 1 (is_final=1): nonce (12 octets): 070707070707070707070707 epoch_key (32 octets): 76dc9f376e4de74d3e402151c7d4fd99 70e67da67e0be1af1a6139602724a77c Sullivan Expires 6 May 2027 [Page 97] Internet-Draft SEAL November 2026 segment_key (32 octets): 76dc9f376e4de74d3e402151c7d4fd99 70e67da67e0be1af1a6139602724a77c segment_aad (24 octets): 00095345414c2d444154410008000000 0000000001000101 ciphertext (20 octets): dfa16e36b7856c93acc30cea28fda135 8b38b82d tag (16 octets): ce6aa0f4745a73da23bbfa7019ff0f6a contrib (32 octets): bbd42ed60d28a789e28b30c2bed0250d b14ba318ba94616a6a9708428398321d accumulator (32 octets): 1b511a906569803ec0846d77f7ed62b7 9114d28c3f969510166a51b442c7f455 snap_acc (32 octets): bd3c2c6b3dd64e30ceb1a3f485be9951 ef7ea470a85954c44786604e41dba365 D.19. Plaintext-Bound Nonce Component Vectors These vectors exercise the optional plaintext-bound nonce construction ([I-D.sullivan-cfrg-raae]) component by component: the plaintext digest pt_digest, the bound digest pt_hash, the framed nonce_ctx, and the final nonce. The construction is encryptor-only and its output is indistinguishable from random mode on the wire, so no end-to-end vector exercises it. Component values are how this appendix checks an implementation byte for byte. The labels are those of [I-D.sullivan-cfrg-raae], and nonce_ctx enters the final derivation as a single framed info element after the payload_info elements. Each block reuses the payload_key, payload_info, and salt of a published cipher-suite block, so the values chain into the payload schedules above. The random_input value stands in for the fresh Random(Nn) draw of the construction, and every block binds the plaintext "Hello, SEAL!". These component vectors are printed in this appendix only and are not part of the end-to-end corpus. D.19.1. AES-256-GCM, HKDF-SHA-256, 16384, Segment 0 The payload schedule is that of Appendix D.1, and the segment_aad is that of its Segment 0. Sullivan Expires 6 May 2027 [Page 98] Internet-Draft SEAL November 2026 plaintext (12 octets): 48656c6c6f2c205345414c21 random_input (12 octets): 0f0f0f0f0f0f0f0f0f0f0f0f is_final 1 A_i ( 0 octets): (empty) pt_digest (32 octets): e66fec4cada0ccdb73930622ef393d5b a05fb73bdd81205a9f828f75e85ded81 pt_hash (32 octets): 072fa800d5069a226a7322c5b3fb704f 8564fd2075dd9de6de274a2b5645faaa nonce_ctx (82 octets): 000a5345414c2d52572d763100080000 0000000000000020072fa800d5069a22 6a7322c5b3fb704f8564fd2075dd9de6 de274a2b5645faaa001800095345414c 2d444154410008000000000000000000 0101 nonce (12 octets): d788503bb616e7a08dc945de D.19.2. AES-256-GCM, TurboSHAKE-256, 65536, Segment 1 The payload schedule is that of the two-segment TurboSHAKE-256 block (Appendix D.15), and the segment_aad is that of its final Segment 1. The segment index is 1, visible in the second framed element of nonce_ctx. The plaintext is the one printed below, not that block's Segment 1 plaintext. Sullivan Expires 6 May 2027 [Page 99] Internet-Draft SEAL November 2026 plaintext (12 octets): 48656c6c6f2c205345414c21 random_input (12 octets): 0f0f0f0f0f0f0f0f0f0f0f0f is_final 1 A_i ( 0 octets): (empty) pt_digest (64 octets): f809e1b9b0e28d0fd1dce5cf9e4aae59 fde2b08a551c311b621323a5d2f3c78d e55c22edcdd091231c4509849acf592a 85ab446dfcff4fac008194e6ef59d9b4 pt_hash (64 octets): ad6a35f94960ee1004391bdfaee16149 ef56bca4fd5b98abfec56fdb80752624 ade63d71f3b15e9aa5dd9fb0ffa8e533 f7ef5caa7620432cf1c1bf7d97611536 nonce_ctx (114 octets): 000a5345414c2d52572d763100080000 0000000000010040ad6a35f94960ee10 04391bdfaee16149ef56bca4fd5b98ab fec56fdb80752624ade63d71f3b15e9a a5dd9fb0ffa8e533f7ef5caa7620432c f1c1bf7d97611536001800095345414c 2d444154410008000000000000000100 0101 nonce (12 octets): 5e5b312aa2e045d4e893deca D.19.3. AES-256-GCM, HKDF-SHA-256, 16384, Non-Final Segment 0 with A_i The payload schedule is that of the two-segment AES-256-GCM, HKDF- SHA-256, 16384 block, which equals that of Appendix D.1. The segment is that block's Segment 0. A_i is the 16-octet ASCII string "segment metadata", so the segment_aad in nonce_ctx is that block's Segment 0 segment_aad followed by the framed A_i. Among the inputs, only is_final and A_i differ from the first block, so pt_digest and pt_hash repeat its values. Sullivan Expires 6 May 2027 [Page 100] Internet-Draft SEAL November 2026 plaintext (12 octets): 48656c6c6f2c205345414c21 random_input (12 octets): 0f0f0f0f0f0f0f0f0f0f0f0f is_final 0 A_i (16 octets): 7365676d656e74206d65746164617461 pt_digest (32 octets): e66fec4cada0ccdb73930622ef393d5b a05fb73bdd81205a9f828f75e85ded81 pt_hash (32 octets): 072fa800d5069a226a7322c5b3fb704f 8564fd2075dd9de6de274a2b5645faaa nonce_ctx (100 octets): 000a5345414c2d52572d763100080000 0000000000000020072fa800d5069a22 6a7322c5b3fb704f8564fd2075dd9de6 de274a2b5645faaa002a00095345414c 2d444154410008000000000000000000 010000107365676d656e74206d657461 64617461 nonce (12 octets): 167034ea5913efb676ebeee9 D.20. Digest Transcript Component Vectors These vectors exercise the digest transcript (Section 5.2) component by component: the schedule values of a SEAL-RO-v1 digest transcript (snap_id 0x0002) context, two segment leaves over specified segment bytes and tags, and the transcript over them. The segment bytes are specified component inputs, not the AEAD output of a full object. The leaf and transcript derivation is what an implementation checks byte for byte. These component vectors are printed in this appendix only and are not part of the end-to-end corpus. The context is a SEAL-RO-v1 object using the digest transcript (snap_id 0x0002), with AES-256-GCM and HKDF-SHA-256, the demo CEK (0xAA repeated) and salt (0x04 repeated) of Appendix D.1, and the default empty G. payload_info is [aead_id 0x0002, segment_max_be 65536, kdf_id 0x0001, snap_id 0x0002, segment_commitment 0x01, nonce_mode 0x01 (derived), epoch_length_u8 0x20, salt]. Sullivan Expires 6 May 2027 [Page 101] Internet-Draft SEAL November 2026 CEK (32 octets): aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 commitment (32 octets): 8bf5df9f63f2955bdc1a376cd3199a05 3653fb26ecc266ccbcfb56203ae54307 snap_key (32 octets): bee28b282450dd38c54bdfa40d98dc9a df5e2af49cf8c99b4c88b6daf7f08664 D.20.1. Leaves and Transcript, n_seg = 2 Segment 0 is non-final with 16 specified ciphertext octets, segment 1 is final with 8. In this derived-nonce example A_i is empty, so the segment AAD is empty and each leaf is the ciphertext digest LH(ct_i), the segment's AEAD tag, and the counter uint64((i << 1) | is_final), 56 octets in all. Here the tag is a specified component input. ct_0 (16 octets): 000102030405060708090a0b0c0d0e0f tag_0 (16 octets): 202122232425262728292a2b2c2d2e2f leaf(0) (56 octets): b726af81e3cadd5c7a486e6a3becc39b 1c5628da64e8145161124bc172a4ad64 202122232425262728292a2b2c2d2e2f 0000000000000000 ct_1 (8 octets): 1011121314151617 tag_1 (16 octets): 303132333435363738393a3b3c3d3e3f leaf(1) (56 octets): 3c36a934a52861d5b6c1da66ab493edb 41f744771e16750ac409b3bed2758f87 303132333435363738393a3b3c3d3e3f 0000000000000003 transcript (32 octets): 362ac32a28544821d1081d4dd305bd1a 853f5f72bcf97ad25d8ec972131f4d7f Sullivan Expires 6 May 2027 [Page 102] Internet-Draft SEAL November 2026 The counter is 0 in leaf(0) and 3 in leaf(1), whose finality bit is set. The transcript's ikm is [snap_key] and its info elements are [...payload_info, commitment, uint64(2), LH(encode(leaf(0), leaf(1)))], with the payload_info elements above. The encode framing binds each leaf's position, and the count element binds the count. The stored keyed snapshot is the transcript. The public snapshot (Section 5.2) takes the same info elements under an empty ikm and the label "snap_transcript_pub". It differs from the transcript: pub_snapshot (32 octets): d5ac3aa35ecb73ffce9d5c443d186965 3752f1ca4ad707bfa847b6f411e17d4b D.21. Epoch Digest Tree Component Vectors These vectors exercise the epoch digest tree (Section 5.3) with four segments in two epochs. The context is a SEAL-RO-v1 object using the epoch digest tree (snap_id 0x0003) with epoch_length 1 (epoch_length_u8 0x01), chosen small so the example has two epochs of two segments. Changing snap_id and epoch_length changes payload_info, so the commitment and snap_key differ from Appendix D.20. The segment bytes and tags are specified component inputs. The context uses the same CEK (0xAA repeated) and salt (0x04 repeated) as Appendix D.20, AES-256-GCM, HKDF-SHA-256, segment_max 65536, derived nonce, and the default empty G. commitment (32 octets): dbd84f656f33d0813709cae78e48cf67 acd550c18dbe06309a05a8dd8a31e7b2 snap_key (32 octets): f2907b32bbcfe226afa5f560717455ad 76875058328280af262ce0529ebae0ee The four derived-mode leaves, with the same component inputs as Appendix D.20 at indices 0 and 1, carry the counters 0, 2, 4, and 7, since only segment 3 is final, so leaf(1) differs from the digest transcript's: Sullivan Expires 6 May 2027 [Page 103] Internet-Draft SEAL November 2026 leaf(0) (56 octets): b726af81e3cadd5c7a486e6a3becc39b 1c5628da64e8145161124bc172a4ad64 202122232425262728292a2b2c2d2e2f 0000000000000000 leaf(1) (56 octets): 3c36a934a52861d5b6c1da66ab493edb 41f744771e16750ac409b3bed2758f87 303132333435363738393a3b3c3d3e3f 0000000000000002 leaf(2) (56 octets): 2c8e6a5e8019913bebc27623854e35e3 2bcd1ef439372403f8492e60d571cad1 404142434445464748494a4b4c4d4e4f 0000000000000004 leaf(3) (56 octets): cbd4067f9d6dc18f7bb5f33675a1b01e 6ce102113947a9dc1045776b8e62d200 505152535455565758595a5b5c5d5e5f 0000000000000007 Epoch 0 is segments 0 and 1, epoch 1 is segments 2 and 3. Each epoch head is d_e = KDF(protocol_id, "snap_epoch", [snap_key], [...payload_info, commitment, uint64(2), LH(epoch_run(e))], 32), the count being 2, the epoch's leaf count, and epoch_run(e) the encode of its leaves: d_0 (32 octets): 669ffe7ce2281f3b13048eff1d6cdf1d 4e32a0c5d834ddddc4c6dc701585ee24 d_1 (32 octets): 44fbe7e8cb17c936b4f229abb5474aa4 a777f00f86541d4f6c31f6f2eb4c8aa3 The snapshot is KDF(protocol_id, "snap_epoch_root", [snap_key], [...payload_info, commitment, uint64(4), LH(d_0 || d_1)], 32): snapshot (32 octets): 4fdcb3d3a1d88e32663b0f37c89a6d79 2e092dfc0f10fa0ca73f8aa7022a75f7 The public heads pd_e = KDF(protocol_id, "snap_epoch_pub", [], [...payload_info, commitment, uint64(2), LH(epoch_run(e))], 32) are taken over the same runs, and the public snapshot is KDF(protocol_id, "snap_epoch_root_pub", [], [...payload_info, commitment, uint64(4), LH(pd_0 || pd_1)], 32) (Section 5.3.1). Each differs from the keyed value over the same inputs: Sullivan Expires 6 May 2027 [Page 104] Internet-Draft SEAL November 2026 pd_0 (32 octets): 783a428f367fa0fa27e70ec634d3c32e 038e36d0e35ab990602c409316f414a3 pd_1 (32 octets): 10a56db4b77b3164b666ef057d5c5f49 8528d68ac4d600cec4bfc057e5f384f6 pub_snapshot (32 octets): bbd1d3f741787232088f49cb50188bb7 14184fc158e6464092be5fe86d95fde2 At the epoch boundary k = 2, with segments 0 and 1 written non-final and the object open, the intermediate snapshots are snapshot(2) = KDF(protocol_id, "snap_epoch_root", [snap_key], [...payload_info, commitment, uint64(2), LH(d_0)], 32) and pub_snapshot(2), the public snapshot at count 2 over LH(pd_0) (Section 5.3.1). They take leaf(0) and leaf(1) as printed, which carry is_final = 0 in the open prefix as in the complete object. The heads run at count 4 begins with d_0, so snapshot(4) extends snapshot(2): snapshot(2) (32 octets): 4cb454fbcf7755403655fab974a323a0 9b304ddd992da05c9dd147788026d7a1 pub_snapshot(2) (32 octets): 138d5914f8f7b55fc3d0771d0335fbbb bb941bbe5e9cb34421831aaf7d19c77f In an object whose segments 0 and 1 were encrypted non-final, the two-segment prefix presented as complete fails the terminal check of [I-D.sullivan-cfrg-raae] with either value, and SnapVerify rejects it declared complete, because leaf(1) would then carry the counter 3. D.22. Negative SnapVerify Vector This vector demonstrates the snapshot integrity check. It takes the honest state of the two-segment AES-256-GCM, HKDF-SHA-256, 65536 vector and flips the first octet of segment 0's stored tag (tag_0), leaving the stored keyed snapshot unchanged. SnapVerify recomputes the accumulator from the two present segment tags ([I-D.sullivan-cfrg-raae]): the tampered tag_0 changes contrib(0), so the recomputed accumulator differs from the honest one and the snapshot recomputed over the observed count no longer equals the stored keyed snapshot. The single constant-time comparison of the recomputed snapshot against the stored value therefore fails, and SnapVerify returns reject. A consumer must treat this entry as expect-reject and must not accept it as a valid snapshot. D.22.1. Tampered Segment Tag, snapshot Not Recomputed Sullivan Expires 6 May 2027 [Page 105] Internet-Draft SEAL November 2026 Source: the two-segment AES-256-GCM, HKDF-SHA-256, 65536 vector; the tamper flips the first octet of segment 0's stored tag (tag_0), with the stored keyed snapshot unchanged. SnapVerify result: reject SnapVerify recomputes the accumulator from the present segment tags, recomputes the snapshot over the observed count, and compares it against the stored keyed snapshot in constant time. The tampered tag_0 changes contrib(0), so the recomputed snapshot differs from the stored value and the comparison fails. The tampered tag_0 and the honest keyed snapshot are regenerated with the rest of the corpus. D.23. SEAL-simple(HKDF-SHA-256, AES-256-GCM) This vector realizes the named instantiation of Section 7 at cipher suite (aead_id 0x0002, kdf_id 0x0001) and matches the implementation sketch of Appendix A byte-for-byte. It uses SEAL-RO-v1, snap_id 0x0000 (no snapshot authenticator), derived nonce mode, epoch_length 32, segment_max 65536, and the linear immutable reduction layout. Sullivan Expires 6 May 2027 [Page 106] Internet-Draft SEAL November 2026 Parameter set: cipher suite: aead_id 0x0002 (AEAD_AES_256_GCM) kdf_id 0x0001 (HKDF-SHA-256) geometry: segment_max 65536 operational: epoch_length 32 nonce_mode derived snap_id 0x0000 (none) payload_info (the KDF frames each element): aead_id ( 2 octets): 0002 segment_max_be ( 4 octets): 00010000 kdf_id ( 2 octets): 0001 snap_id ( 2 octets): 0000 segment_commitment ( 1 octets): 00 nonce_mode ( 1 octets): 01 epoch_length_u8 ( 1 octets): 20 salt (32 octets): 04040404040404040404040404040404 04040404040404040404040404040404 Payload schedule: commitment (32 octets): 50e2ed8d8211bc77be4a9c48fc39b7c2 08f2a7f7248dc13767024999a0ec760b payload_key (32 octets): b03d7bedadce359c341811ee803a1887 77070819d75e287eff5efd785f774654 nonce_base (12 octets): 42ccc572f4d26f10705d3604 Segment 0 (is_final=1): nonce (12 octets): (derived from nonce_base, not stored) 42ccc572f4d26f10705d3605 epoch_key (32 octets): d01604cc3ad94b9a8bae8b25d5068623 37d97cbf072e29a7d13caa5879feac26 segment_key (32 octets): d01604cc3ad94b9a8bae8b25d5068623 37d97cbf072e29a7d13caa5879feac26 segment_aad ( 0 octets): (empty) ciphertext (12 octets): 9b7de7bcea0031343291e234 tag (16 octets): c2ca31476b162aa7ce795514a78c1b15 Sullivan Expires 6 May 2027 [Page 107] Internet-Draft SEAL November 2026 Appendix E. Changes Since -00 * The strict authenticators' leaf is [LH(ct_i)] || tag_i || nonce_field(i) || segment_aad(i, is_final, A_i): the stored nonce in random nonce mode or the counter (i << 1) | is_final in derived nonce mode, then the segment AAD. Metadata entries hold its leading fields in leaf order, and epoch runs frame each leaf. The per-suite nonce-injectivity analysis is withdrawn. The ct_i and tag_i split is stated beside the leaf definition. * The epoch digest tree's outsider forgery bound is stated as a closed list of three events; the key-holder bounds name the ideal- hash, sponge and long-message cases. * Binding limits and budgets use the aggregate accounting of the companion; the AES-GCM random-nonce cap and the departure from RFC 8439 for random ChaCha20-Poly1305 nonces are stated. * The masked multiset hash's writer check and the SEAL-AO-v1 current-count rule is stated. * The layouts leave no interior segment shorter than the length they fix for its position. Under a mutable profile a writer meets this by rewriting a short terminal segment before an append, and a truncation to a nonempty prefix re-marks the new last segment as final. SEAL-AO-v1 forbids truncation rather than a reduced count. * The split layout places the aux region after the commitment. * An authenticator with a strict ordering has snapshots at every count while the object is open, and the epoch digest tree's late joiner adopts heads at an epoch boundary. * The digest transcript and the epoch digest tree define public snapshots. Every value of the two authenticators, keyed or public, is one KDF over the payload_info elements, the commitment, a count, and the LH digest of a run of leaves or heads, under snap_key or an empty ikm. An epoch head is the digest transcript's formula over its epoch. A new section describes streaming with prefix verification. * RFC 10032 (AEGIS) and RFC 8126 are normative; the designated expert review criteria are stated. * The vector files are regenerated for this revision; the plaintext- bound component vectors are regenerated over the framed segment AAD and one at a non-final segment with A_i is added. Sullivan Expires 6 May 2027 [Page 108] Internet-Draft SEAL November 2026 * Terms: keyed snapshot, public snapshot, intermediate snapshot, prefix verification, and ciphertext digest. Acknowledgments The author thanks Andrés Fábrega, Thomas Ristenpart, Gregory Rubin, Richard Barnes, Brendan McMillion, Thibault Meunier, Kenny Paterson, Christopher Patton, Martin Thomson, Stanislav V. Smyshlyaev, and Christopher A. Wood for their reviews, comments, and discussions. Author's Address Nick Sullivan Cryptography Consulting LLC Email: nicholas.sullivan+ietf@gmail.com Sullivan Expires 6 May 2027 [Page 109]