| Internet-Draft | SEAL | July 2026 |
| Sullivan | Expires 24 January 2027 | [Page] |
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.¶
This note is to be removed before publishing as an RFC.¶
Discussion of this document takes place on the Crypto Forum Research Group mailing list (cfrg@ietf.org), which is archived at https://mailarchive.ietf.org/arch/browse/cfrg.¶
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 24 January 2027.¶
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.¶
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 snapshot value that binds the whole segment set as the writer last 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 segment commitment is set, and external authentication of the snapshot value (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 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 10).¶
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, and the per-segment leaf), and the advantage notation used for the security bounds.¶
The suites below are examples for the SEAL construction, whose concrete framing and procedures are defined in [I-D.sullivan-cfrg-raae].¶
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 |
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 [I-D.irtf-cfrg-aegis-aead]. The AEGIS code points 0x0021 and 0x0024 are early allocations that firm up when that I-D is published as an RFC.¶
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).¶
No SEAL AEAD is natively committing (the committing column). An instantiation that must bind its segments against a CEK holder sets the segment commitment ([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) and a snapshot authenticated outside the construction, and a linear authenticator MUST NOT set it (Section 6). Only the attachment instantiations 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]).¶
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 |
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 | Snapshot value |
|---|---|---|
| 0x0000 | none | none produced |
| 0x0001 | masked multiset hash | snapshot, per Section 5.1 |
| 0x0002 | digest transcript | snapshot, per Section 5.2 |
| 0x0003 | epoch digest tree | snapshot, per Section 5.3 |
snap_id 0x0000 selects no snapshot authenticator, a null snapshot: no snapshot value 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:¶
| nonce_mode | Name |
|---|---|
| 0x00 | random |
| 0x01 | derived |
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".¶
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.¶
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, and carries SEAL's snapshot machinery per the selected snap_id. Under 0x0001 that is the accumulator with its mask; under 0x0002 the recomputed snapshot value over the leaf list; under 0x0003 the epoch-heads region and the recomputed snapshot value. All three include SnapVerify. Unauthorized truncation surfaces at two points: the terminal finality check of [I-D.sullivan-cfrg-raae], and SnapVerify over the complete segment set, which also binds the segment count.¶
SEAL-AO-v1 is the append-only profile. "Append-only" names the writer's discipline, not a guarantee of tamper evidence. 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 reduce n_seg. It changes length by the append direction of [I-D.sullivan-cfrg-raae] alone ([I-D.sullivan-cfrg-raae]), whose re-mark carries the segment's existing plaintext and changes 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). "Immutable" names the writer's write-once discipline, not a guarantee of tamper evidence. 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, and MUST NOT retry an object it did not complete under the same salt: it MUST draw a fresh one ([I-D.sullivan-cfrg-raae]).¶
The retry rule is what keeps each index to one finality form, which the truncation check of [I-D.sullivan-cfrg-raae] rests on. Without it an interrupted write whose source has since grown would place the old terminal segment's is_final = 1 ciphertext at an index the retry writes non-terminal. Neither encryption reuses a pair, so nothing else in this document would forbid it, and the orphan would let a holder of those bytes present a truncation that verifies.¶
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. Any such authenticator binds the whole segment set in its snapshot value. A consuming protocol that authenticates a strict snapshot carrying the segment commitment obtains from it per-segment origin authentication ([I-D.sullivan-cfrg-raae]). 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. Under any snapshot but a null one, SnapVerify additionally binds the count.¶
| 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 |
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 snapshot value 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 segment commitment 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]).¶
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 and its snapshot value 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.¶
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.¶
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.¶
In a linear layout the salt, commitment, snapshot value, aux region, 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 snapshot value (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.¶
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 snapshot value, 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).¶
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 snapshot value and the aux region, then streams segments. After all segments are written the writer seeks back and writes the snapshot value 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.¶
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.¶
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 snapshot value, the authenticator's aux region, and one metadata entry per segment:¶
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 segment's stored nonce and its AEAD tag:¶
The bracketed nonce is present only in random nonce mode, and the bracketed ciphertext digest LH(ct_i) only when the segment commitment is set. The tag is always present, Nt = 16 octets in every SEAL suite (Table 1). The fields are stored in leaf order: under a derived nonce (Np = 0) the metadata entry is exactly the leaf, LH(ct_i) || tag_i, so an implementation folds the stored bytes directly. Each metadata entry holds the Np-octet presented nonce and the Nt-octet tag, so meta_len = Np + Nt octets, plus Nh when the segment commitment is set. A random-nonce entry sets Np = Nn and is Nn + Nt octets. A derived-nonce entry recomputes the nonce from the key schedule, so Np = 0 and the entry is Nt octets. The per-suite values, for a metadata entry with no stored leaf, are:¶
| AEAD | nonce mode | meta_len |
|---|---|---|
| AES-128-GCM, AES-256-GCM, ChaCha20-Poly1305 | random (Nn = 12) | 28 |
| AEGIS-256, AEGIS-256X2 | random (Nn = 32) | 48 |
| AES-256-GCM-SIV | derived | 16 |
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 the authenticator's own whole-object region, held outside both the snapshot value and the per-segment metadata entries. Per-segment state does not count toward it: a stored leaf sits in the metadata entry and is already carried by meta_len. aux is whatever whole-object state the authenticator keeps beyond its snapshot value, and it takes one of three forms:¶
none, so aux is 0. A null snapshot keeps nothing, and the digest transcript recomputes over the leaves rather than keep a summary.¶
a fixed-size summary, so aux is a constant. The masked multiset hash keeps its masked accumulator, Nh octets.¶
per-region state whose size follows the count, so aux depends on the object. The epoch digest tree keeps its heads, n_ep * Nh octets, growing with the object through n_ep.¶
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.¶
With the default commitment_length = Nh, this specializes by authenticator. meta_len is not uniform across them: it grows by Nh when the segment commitment 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 snapshot value from the header alone, then seeks to any segment using these offsets. Because the authenticator's per-segment leaves live in the metadata entries (the tag alone, or LH(ct_i) || tag_i when the segment commitment is set), 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 snapshot value and the epoch-heads region together, then reads one epoch's leaf run to check a segment.¶
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, the n_seg per-segment metadata entries (each meta_len octets, broken out in Figure 4), and the snapshot value last. The authenticator's per-segment leaves live in the metadata stream ([I-D.sullivan-cfrg-raae]): the tag alone, or LH(ct_i) || tag_i when the segment commitment is set. A reader therefore authenticates the snapshot by reading only the metadata stream, never the data stream. When the authenticator keeps whole-object state, the metadata stream also carries its aux region: the masked accumulator (Nh octets) or the epoch heads (n_ep * Nh octets). The metadata stream is written once n_seg is known, so it carries a count-dependent aux as readily as a fixed one. Section 4.2 gives the region's byte layout.¶
Because neither stream embeds the other, both grow by appending. Extending a message appends one ciphertext segment to the data stream, appends one metadata entry to the metadata stream, and rewrites the trailing snapshot value. Truncating drops the tail of each stream and rewrites the snapshot value. Neither operation shifts an existing ciphertext segment, which the in-place aligned layout cannot avoid once the header grows.¶
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 snapshot value or aux region, so it supports only a null snapshot, which SEAL-log selects (Section 7).¶
The length before segment i is uint16(len_i), the octet length of ct_i. Two octets suffice because ct_i is at most segment_max, 16384 for SEAL-log. 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. The linear layout stores a segment inline with no length, and the aligned layout places segment i at offset i * segment_max, so neither can represent a short interior segment. The append-only re-mark produces one: appending to an object whose terminal segment is short re-marks that segment (Section 3.2), preserving its plaintext and hence its length, so it becomes a short interior segment.¶
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.¶
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 snapshot value 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 inside meta_len):
32 + commitment_length + Nh + (Nt + Nh) * n_seg
snap_id 0x0003 (Na = Nh, aux = n_ep * Nh stored):
32 + commitment_length + Nh + n_ep * Nh + (Nt + Nh) * n_seg
¶
When the segment commitment ([I-D.sullivan-cfrg-raae]) is set, each aligned or split metadata entry also carries LH(ct_i). 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.¶
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.¶
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].¶
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.¶
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 snapshot value. 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 snapshot value, 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.¶
To 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. The wrapped_acc and snapshot MUST come from locally trusted snapshot state ([I-D.sullivan-cfrg-raae]).¶
The snapshot is Nh octets, so Na = Nh, as it is for the other two authenticators ([I-D.sullivan-cfrg-raae] defines Na in general). wrapped_acc is not part of it. A verifier 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, which makes it a summary in the sense of Section 10, 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 a different authenticator's aux region, and no object carries both.¶
Masking it is still required (Appendix B). Moving the accumulator out of the snapshot value changes where it is written, not whether it is exposed.¶
SnapVerify runs only after the verifier has re-derived and checked the commitment as part 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 or any value derived from it, 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 SnapVerify ([I-D.sullivan-cfrg-raae]). The forgery bound is in Section 6.1 and the rollback limitation in [I-D.sullivan-cfrg-raae].¶
This snapshot authenticator is a per-object digest transcript: one keyed KDF evaluation over the ordered list of per-segment digests. It has no accumulator and no mask, and it recomputes over the full leaf list on any rewrite. Its distinguishing property is that its ordering is strict: a fixed, published snapshot cannot be made to verify a different segment set, even by a party that holds the CEK and every key derived from it. When the segment commitment is set, so the leaf carries LH(ct_i), the snapshot additionally binds each segment's ciphertext bytes, not only its AEAD tag (Section 6.2). A consuming protocol that authenticates the snapshot value, for example by binding it into a signed reference or manifest, extends that binding to per-segment origin authentication.¶
Its per-segment input is the segment leaf ([I-D.sullivan-cfrg-raae]). The leaf carries the AEAD tag always, and prepends the ciphertext digest LH(ct_i) when the segment commitment is set:¶
leaf(i) = [LH(ct_i)] || tag_i¶
LH is the over-large-field digest of [I-D.sullivan-cfrg-raae], so where the segment commitment 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, and leaf(i) is the Nh + Nt octet concatenation of that digest with the tag. Without it the leaf is the Nt-octet tag alone and the transcript binds the tag set 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]).¶
The leaf binds the segment's index and finality, and the presented nonce, through the tag. The tag authenticates the segment AAD, which carries the index and is_final in random nonce mode and is empty in derived nonce mode where the nonce carries them ([I-D.sullivan-cfrg-raae]), and the tag is a function of the presented nonce, so a segment cannot be reinterpreted under a different nonce without changing the tag and hence the leaf. Position within the object is bound by the ordered transcript below and the count by n_seg. Because the tag binds the index and finality in either nonce mode, the digest transcript places no restriction on the nonce mode.¶
The snapshot value is one keyed derivation over the commitment, the count, and the ordered leaf list:¶
snapshot = KDF(protocol_id, transcript_label, [snap_key],
[commitment, uint64(n_seg),
leaf(0), ..., leaf(n_seg-1)], Nh)
¶
so Na = Nh. The digest transcript fixes one label, transcript_label = "snap_transcript", distinct from all other SEAL labels under the encode frame ([I-D.sullivan-cfrg-raae]). The injective framing of the ordered list binds each leaf's position and the count, so a leaf cannot move to another index without changing the transcript. The snap_key ikm makes the value unforgeable without the key ([I-D.sullivan-cfrg-raae]). The commitment element repeats the object's commitment ([I-D.sullivan-cfrg-raae]) inside the transcript. For SnapVerify alone it is redundant, because snap_key already binds the CEK and salt. It is load-bearing for a consuming protocol that lifts the snapshot value into a signature or MAC, which then carries the full object context (CEK, payload_info, and G) and cannot be replayed against another object (Section 6.2).¶
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.¶
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 over the stored segment i
snapshot_calc = KDF(protocol_id, transcript_label, [snap_key],
[commitment, uint64(n_seg),
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 leaf list. Any change to the segment set recomputes the transcript over the new leaf list and re-publishes the snapshot value. A write-once profile computes it once, at encryption, and only verifies it thereafter.¶
An object with n_seg = 0 is a valid empty object under the digest transcript. The leaf list is empty and the snapshot value is KDF(protocol_id, transcript_label, [snap_key], [commitment, uint64(0)], Nh), so the count is bound and truncation to the empty object is distinguishable from a legitimate empty object.¶
verify above recomputes each leaf from the stored segment bytes, so whole-object SnapVerify reads the full object. A layout or consuming protocol MAY store or carry the leaf list itself: a layout MAY hold leaf(i) in each per-segment metadata entry, and a consuming protocol MAY carry the list beside the object, for example in a manifest. A reader MAY then verify the transcript over the stored or carried leaves without reading any ciphertext, and MUST compare leaf(i), recomputed from the one segment it reads, against the stored or carried entry 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 origin authentication from one authenticated snapshot value.¶
A modified segment, tag, or count changes the recomputed transcript, and a different key or parameter context fails the commitment check that precedes SnapVerify ([I-D.sullivan-cfrg-raae]). The forgery and binding arguments are in Section 6.2.¶
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 7). The design has the digest transcript's binding properties and adds this bounded random-access verification for large objects.¶
The leaf is the same as in Section 5.2:¶
leaf(i) = [LH(ct_i)] || tag_i¶
d_e is the epoch head over one epoch's leaves, and the snapshot is the keyed digest over d_0 .. d_{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.¶
The epoch head d_e is a keyed digest over that epoch's leaves:¶
epoch_run(e) = leaf(e * 2^r) || ... || leaf(last index in epoch e)
d_e = KDF(protocol_id, epoch_head_label, [snap_key],
[LH(epoch_run(e))], Nh)
¶
The leaves are fixed length per object (Nh + Nt octets with the segment commitment set, Nt octets without) and the epoch heads are fixed length (Nh octets), so each run parses unambiguously given its element count, which n_seg and epoch_length fix.¶
An epoch head needs no index or count of its own. Its position is bound by where d_e sits in the ordered heads_run, exactly as a leaf's position is bound by the ordered transcript in Section 5.2. The final epoch's size is fixed by n_seg and epoch_length, both already bound (n_seg in the snapshot below, epoch_length in snap_key through payload_info), so a partial final epoch needs no explicit count.¶
Because the epoch heads carry no index or count of their own and the segmentation is recovered only from n_seg and epoch_length, a profile that selects the epoch digest tree MUST bind epoch_length into snap_key. SEAL satisfies this through payload_info, which carries epoch_length into the snap_key derivation. A profile that left epoch_length unbound would let a key holder present one heads_run under a different fan-out, defeating the position and count binding above.¶
A full leaf run can be larger than the 0xFFFE-octet frame literal ([I-D.sullivan-cfrg-raae]), so LH compresses it to a fixed Nh octets before it enters the KDF, and every element the KDF sees stays at most Nh octets.¶
The snapshot value is a keyed digest over the epoch heads:¶
heads_run = d_0 || d_1 || ... || d_{n_ep-1}
snapshot = KDF(protocol_id, head_label, [snap_key],
[commitment, uint64(n_seg), LH(heads_run)], Nh)
¶
so Na = Nh. LH pre-hashes the heads run for the same reason. The epoch digest tree fixes two labels, epoch_head_label = "snap_epoch" and head_label = "snap_epoch_root", each distinct from all other SEAL labels under the encode frame ([I-D.sullivan-cfrg-raae]). The commitment and count are bound as in Section 5.2, and the snap_key ikm makes the value unforgeable without the key ([I-D.sullivan-cfrg-raae]).¶
Because the leaf binds the tag (Section 5.2), the epoch digest tree places no restriction on the nonce mode.¶
A reader stores the n_ep epoch heads in the header alongside the snapshot value, and the per-segment leaves in the metadata entries. The snapshot value 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 leaf. 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 per-segment leaves are contiguous within an epoch, so one seek reads the whole epoch. A layout that interleaves the leaves with the ciphertext does not have this property, and a reader gathers an epoch's leaves 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.¶
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],
[LH(epoch_run(e))], Nh)
snapshot_calc = KDF(protocol_id, head_label, [snap_key],
[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), which follows the
snapshot value in the header
head_calc = KDF(protocol_id, head_label, [snap_key],
[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 leaf run, min(2^r, n_seg - e * 2^r) leaves,
from segment e * 2^r's metadata entry
;; one aligned read
d_calc = KDF(protocol_id, epoch_head_label, [snap_key],
[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
check leaf_calc equals the stored 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 7). 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.¶
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 snapshot value, 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 the value is computed once at encryption. 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 leaf, the new epoch head, and the new snapshot value.¶
An object with n_seg = 0 has n_ep = 0 and an empty heads run:¶
snapshot = KDF(protocol_id, head_label, [snap_key],
[commitment, uint64(0), LH("")], Nh)
¶
so the count is bound and truncation to the empty object is distinguishable.¶
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 (Np = 0, meta_len = Nt + Nh). A random-nonce entry has meta_len = Np + Nt + Nh. 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 |
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.2.¶
The random-access benefit is realized only when epoch_length is in the grouping range of Table 7. 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 7 value and accounts for the resulting per-epoch-key write budget in [I-D.sullivan-cfrg-raae].¶
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, including the mu-PRF cost of its snap_key derivations.¶
Every snapshot value 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 ordered leaf list under the digest transcript, and the epoch heads 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.¶
SEAL's masked multiset hash publishes a snapshot value that is the 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.¶
Its forgery advantage is the Adv_snap term of the master ra-ROR bound ([I-D.sullivan-cfrg-raae]) for snap_id 0x0001, 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 snapshot value from the observed count and the recomputed accumulator and compares it against the stored snapshot value in one constant-time comparison. A variable-time comparison would reveal the position of the first differing octet, letting an adversary who retries tampered inputs learn the expected snapshot value one octet at a time.¶
SEAL's digest transcript publishes one keyed KDF evaluation over the commitment, the segment count, and the ordered leaf list (Section 5.2). Its transcript label is distinct under the encode frame, and the injective framing binds each leaf's position and the count.¶
Unforgeability without snap_key rests on the same multi-user PRF assumption as the rest of the key schedule. The transcript is a single PRF evaluation over the framed input, so its term in [I-D.sullivan-cfrg-raae] is a fresh-input forgery term alone: there is no accumulator to recombine, no mask, and no birthday term in the number of published snapshots.¶
The leaf gives the transcript two bindings against a key holder, on independent threat axes. The first is content binding, and it holds only when the segment commitment 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, so the ciphertext binding rests on the collision resistance of LH for any segment length. 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 segment commitment the transcript binds the tag set 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 is exhibit a second segment set that verifies under a fixed, already-published transcript value.¶
The second binding is the presented nonce, through the tag. A non-committing AEAD lets a key holder reinterpret a fixed ciphertext: holding ct_i unchanged and presenting a different stored nonce yields a different plaintext under the same key ([I-D.sullivan-cfrg-raae]), with a tag the key holder recomputes. The ciphertext bytes do not change, so a leaf of LH(ct_i) alone would not detect the substitution. The tag does change, because it is a function of the nonce, so a leaf that carries the tag binds the presented nonce and rejects the reinterpretation. How firmly it binds depends on the nonce mode and, in random nonce mode, on the AEAD's nonce and tag widths. In derived nonce mode the nonce is recomputed rather than stored, so it cannot be presented differently and no reinterpretation arises. In random nonce mode the stored nonce can be changed: for the narrow-nonce suites, whose nonce width Nn is at most the tag width Nt, the tag is effectively injective in the nonce and the binding is structural, while for a wide-nonce suite (Nn greater than Nt) a nonce change can preserve the tag only by a 2^(8*Nt) search, so the binding is 2^(8*Nt) rather than structural. An instantiation that sets the segment commitment in random nonce mode over a wide-nonce suite accepts that bound. The tag also binds the segment index and finality, through the segment AAD in random nonce mode and through the nonce in derived nonce mode ([I-D.sullivan-cfrg-raae]).¶
This nonce binding is why the digest transcript places no restriction on the nonce mode. In random nonce mode the nonce is stored and a key holder could otherwise move it, and the tag in the leaf closes that. In derived nonce mode the nonce is recomputed from the key schedule and cannot be moved independently, so the tag is redundant there but not harmful. A leaf without the tag, under a non-committing AEAD in random nonce mode, would be forgeable by a key holder, so SEAL's leaf carries the tag in every case.¶
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 own 2^(4*Nh) 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.¶
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 k-list bound. What it cannot do is change that bound, which the accumulator width and the segment count fix, not what each contribution is computed over.¶
A linear authenticator therefore MUST NOT carry the segment commitment. 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 conditions: the snapshot value 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 equivocation floor at four segments. The ordering is settled by snap_id in this document (Section 10), 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 snapshot value is lifted into a stronger statement.¶
That lifting is the signing layer this section requires for sender attribution. A signature or MAC over a snapshot value 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. An application MUST NOT rely on a signature over the masked multiset hash's snapshot value for content attribution among CEK holders. The ciphertext digest is what grounds content binding on collision resistance. With the segment commitment set, a fixed, authenticated digest transcript also strengthens position binding. It pins each segment's ciphertext to its index, so no one, not even a party that holds or chose the CEK, can take a valid segment and make it verify at a different index. The base construction pins position only as strongly as the AEAD commits under an adversarial key, which the SEAL suites do not ([I-D.sullivan-cfrg-raae]), so a CEK holder can craft one ciphertext that opens at two positions. The transcript closes that gap by grounding position on collision resistance instead. In the terms of [I-D.sullivan-cfrg-raae], this is the position-commitment notion ra-CMT-p, restored against adversarially chosen keys. The leaf then carries both the ciphertext digest and the tag, neither redundant, since no SEAL suite carries a commitment claim ([I-D.sullivan-cfrg-raae]) and all admit random nonce.¶
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.¶
The epoch digest tree (Section 5.3) has the same per-segment leaf and the same key as the digest transcript, so its content, nonce, and position bindings are those of Section 6.2.¶
The two-level fold rests on the same collision-resistance assumption. An epoch head binds its epoch's leaf run through LH, and the snapshot binds the epoch heads through LH, so a forged snapshot requires an LH collision at one of the two levels or a snap_key PRF forgery on the head. The snapshot binds the epoch heads in order and binds n_seg, so an epoch head cannot move to another position and the segmentation is fixed by n_seg and epoch_length.¶
The forgery term without snap_key is the digest transcript's single fresh-input PRF term, with no birthday term in the number of published snapshots. Against a key holder, and where the segment commitment is set, substituting content under a fixed published snapshot requires an LH collision against that fixed target, a second preimage, at one of three target classes: the n_seg leaves it shares with the digest transcript, the n_ep epoch heads, and the single root. The work factor is 2^(8*Nh) per target with a benign multiplicity of n_seg + n_ep + 1, so the two fold levels add the epoch and root targets to the transcript's leaf targets without lowering the per-target strength. A writer that chooses both openings of one snapshot instead faces a birthday collision at 2^(4*Nh), the same equivocation floor as the digest transcript, which the fold does not lower. The formal two-level reduction, the snap_key PRF term on the head and the LH 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.¶
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.¶
| instantiation_id | Name | Profile | segment_max | nonce_mode | epoch | snap_id | layout | segment commitment |
|---|---|---|---|---|---|---|---|---|
| 0x0001 | SEAL-simple | SEAL-RO-v1 | 65536 | derived | 32 | 0x0000 | linear | no |
| 0x0002 | SEAL-attachment | SEAL-RO-v1 | 65536 | derived | 10 | 0x0003 | aligned | yes |
| 0x0003 | SEAL-attachment-small | SEAL-RO-v1 | 65536 | derived | 10 | 0x0002 | aligned | yes |
| 0x0004 | SEAL-editable | SEAL-RW-v1 | 65536 | random | 16 | 0x0001 | linear | no |
| 0x0005 | SEAL-memory | SEAL-RW-v1 | 16384 | random | 16 | 0x0001 | aligned | no |
| 0x0006 | SEAL-disk | SEAL-RW-v1 | 16384 | random | 16 | 0x0001 | split | no |
| 0x0007 | SEAL-compact | SEAL-RW-v1 | 16384 | derived | 16 | 0x0001 | aligned | no |
| 0x0008 | SEAL-log | SEAL-AO-v1 | 16384 | derived | 16 | 0x0000 | append-log | no |
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 segment commitment 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.¶
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 never rewrites, so its 16 bounds only 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 7 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 7 guidance, which gives 9 there rather than 10: 2^10 * (Nt + Nh) 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 snapshot value. 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, for objects up to about 128 GiB at the recommended parameters ([I-D.sullivan-cfrg-raae]).¶
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 ([I-D.sullivan-cfrg-raae]), 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.¶
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).¶
For each profiled suite the binding limit is the smallest applicable usage-limit class of [I-D.sullivan-cfrg-raae], at a 2^-32 advantage target:¶
| Suite / mode | Binding limit | Budget | Failure mode |
|---|---|---|---|
| AES-128-GCM, random | random-nonce collision | ~2^32 per epoch key | confidentiality |
| AES-256-GCM, random | random-nonce collision | ~2^32 per epoch key | confidentiality |
| ChaCha20-Poly1305, random | random-nonce collision | ~2^32 per epoch key | confidentiality |
| AES-256-GCM-SIV, derived, distinct segments | MRAE distinct-nonce | ~2^48 per epoch key | confidentiality and integrity |
| AES-256-GCM-SIV, derived, hot segment | fixed-nonce data volume | ~2^36 per segment at 64 KiB | confidentiality |
| AEGIS-256, random | forgery bound | ~2^83 per key | integrity |
| AEGIS-256X2, random | forgery bound | ~2^83 per key | integrity |
The per-epoch-key and per-key counts hold at any segment size. The two length-dependent figures, the hot-segment data volume and the forgery bound, are worst-case at segment_max and only rise for shorter segments.¶
The 96-bit-nonce suites bind on random-nonce collision. AEGIS makes nonce collision negligible with a 256-bit nonce (~2^112) and binds on forgery. A derived-nonce MRAE AEAD has the two limits above, the distinct-derived-nonce ceiling and the fixed-nonce data-volume cap, not a single shared pool.¶
Once a named instantiation (Section 7) fixes the segment size, the nonce mode, and the epoch_length, the classes above set a concrete write limit a deployment reads off the table below. Object size is not that limit: every instantiation grows to the 2^63-segment index ceiling (about 2^79 octets at segment_max 65536, 2^77 at 16384) through epoch rotation. The limit reached first is how many times one segment may be rewritten.¶
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. A derived-nonce MRAE suite (AES-256-GCM-SIV) instead gives each segment its own fixed-nonce budget that does not divide by epoch_length ([I-D.sullivan-cfrg-raae]), so its hot and average figures coincide.¶
| Instantiation | Segment size | Rewrites per hot segment | Avg rewrites per 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 | ~2^38 | ~2^38 |
| SEAL-log | 16 KiB | write-once | write-once |
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. Two cases depart from them. An AEGIS suite makes nonce collision negligible with its 256-bit nonce, so its rewrite count is bounded by forgery rather than by the epoch-key pool. A derived-nonce MRAE suite (AES-256-GCM-SIV, as SEAL-compact takes) replaces the shared pool with the per-segment fixed-nonce budget in the table. SEAL-log is write-once for a different reason: SEAL-AO-v1 derives a fresh nonce for each index and finality bit, so the per-append re-mark re-encrypts a segment under a new nonce rather than spending its budget, at any derived-nonce suite.¶
Integrity does not degrade with write volume: the per-query forgery limits come from [I-D.irtf-cfrg-aead-limits] for the GCM and ChaCha20-Poly1305 suites, [RFC8452] for AES-256-GCM-SIV, and the AEGIS analysis, and run from about 2^59 to 2^83 forgery attempts at the 2^-32 target.¶
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. The suites, layouts, and named instantiations in this document inherit that analysis and add no security argument of their own beyond the parameter choices recorded below. An implementer needs the companion for the threat model and the assumptions on which every claim in this document rests.¶
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 or payload_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 the extractor-then-PRF analysis of HKDF is in [RFC5869]. 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.¶
Two concrete-specific analyses have their own sections in this document. Each snapshot authenticator instantiation (Section 5) realizes the abstract Adv_snap term of the master ra-ROR bound, and its per-instantiation forgery analysis is in Section 6. The safe-use limits of the profiled suites, derived from the companion's bounds, are collected in Section 8. Staying within the binding limit for a suite and mode preserves the stated confidentiality or integrity margin, and exceeding it degrades the corresponding property.¶
Suite and profile selection carries the trade-offs set out in Section 3.2.1. A 96-bit-nonce random suite binds on nonce-collision probability, whereas a 256-bit-nonce AEGIS suite makes collision negligible and binds on its forgery bound; a derived-nonce MRAE suite replaces the collision limit with the distinct-nonce and fixed-nonce data-volume limits of Section 8; and the choice of snapshot authenticator fixes what a reader can detect (Section 5). These are choices among the regimes the companion analyzes, not departures from the raAE guarantees.¶
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 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.¶
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.¶
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, and a serialization layout, and leaves the cipher suite to the referencing protocol (Section 7). The registry has the columns of Table 8, namely instantiation_id, Name, Profile, segment_max, nonce_mode, epoch, snap_id, layout, and segment commitment, plus a Reference column. Its initial contents are the entries of Table 8, 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 snapshot value 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 snapshot value below the cost of a collision: linear if the construction offers an algebraic route, strict if finding one is a collision problem, and n/a for an authenticator that produces no snapshot value. The analysis behind each value is in Section 6.¶
The value a registrant chooses here settles the segment-commitment 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.¶
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 snapshot value. The initial contents are:¶
| snap_id | Name | Na | ordering | summary | Reference |
|---|---|---|---|---|---|
| 0x0000 | none | 0 | n/a | none | This document |
| 0x0001 | masked multiset hash | Nh | linear | accumulator, masked | This document |
| 0x0002 | digest transcript | Nh | strict | none | This document |
| 0x0003 | epoch digest tree | Nh | strict | n_ep * Nh epoch heads | This document |
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 9).¶
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 [I-D.irtf-cfrg-aegis-aead]. TurboSHAKE-256 (0x0013) is a pending early allocation in the HPKE KDF registry per [I-D.ietf-hpke-pq], and its entry firms up when that draft publishes as an RFC. The SEAL normative references to those drafts are references to works in progress, so publication of this document waits on theirs.¶
| Registry | Code point | Algorithm | Status |
|---|---|---|---|
| AEAD Algorithms | 0x0021 | AEGIS-256 | assigned |
| AEAD Algorithms | 0x0024 | AEGIS-256X2 | assigned |
| HPKE KDF | 0x0013 | TurboSHAKE-256 | early allocation |
Future SEAL profiles MAY consume additional entries from the AEAD Algorithms and HPKE KDF registries without revising this document.¶
This appendix is informative. It specializes [I-D.sullivan-cfrg-raae] for the SEAL-simple named instantiation (Section 7), giving the encrypt and decrypt blocks once and two parameter tables that instantiate them, so an implementer of the profile can build a conforming encryptor and decryptor from this appendix.¶
| 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) |
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.¶
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: 8 YiB (2^83 octets) is the AES-256-GCM budget across all 2^31 epoch keys, each held to a 2^-32 advantage (2^52 octets per key). A full 512 ZiB (2^79-octet) object uses 1/16 of that, a ratio fixed by the SEAL-simple choice of epoch_length 32.¶
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:¶
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})
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)
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. Once that vector is regenerated for the current payload_info, a conforming implementation reproduces its commitment, payload_key, and nonce_base, and the per-segment nonce, ciphertext, and tag, from the fixed inputs.¶
The same instantiation admits SEAL-simple(TurboSHAKE-256, AEGIS-256), reusing existing code points (aead_id = 0x0021, kdf_id = 0x0013) with no new registrations.¶
| 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) |
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 follows AEGIS-256's forgery bound, ~2^83 per key, which does not depend on nonce mode (Table 9), in place of the AES-256-GCM tag-forgery formula. The one-step TurboSHAKE-256 derivations are exercised by the component vectors in Appendix D.15.¶
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, and the snapshot, and wins by making SnapVerify accept a (segment set, snapshot) pair other than the writer's current state. Whole-object rollback to an earlier honest state is excluded and is the application's freshness responsibility ([I-D.sullivan-cfrg-raae]).¶
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 SnapVerify does not surface the recovered accumulator (Section 5.1).¶
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 <= Adv_muPRF_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 argument relies on segment AEAD authenticity, already charged as Adv_muROR ([I-D.sullivan-cfrg-raae]), and adds no term beyond Adv_snap. 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]).¶
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, stores Nh octets of snapshot value 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 different authenticator under its own snap_id ([I-D.sullivan-cfrg-raae]).¶
The masked accumulator is stored in the aux region so a stateless writer can resume the O(1) update from it, rather than holding it in trusted state or re-reading every tag. Storing it 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. 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, each one segment_max or less: the target epoch's leaf run and the list of epoch heads. 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, 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.¶
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. 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.¶
This appendix is informative.¶
All vectors except Appendix D.23 use protocol_id = "SEAL-RW-v1"; Appendix D.23 uses "SEAL-RO-v1". Every vector uses CEK = 32 octets of 0xAA and salt = 32 octets of 0x04. Each 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 snapshot value 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 0x03 and 0x07 repeated to the AEAD nonce length. 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 block exposes the intermediate epoch_key and segment_key per segment. Appendix D.1 gives the full KDF trace for one commitment. The other blocks list schedule outputs only. The twenty computed positive vectors (eighteen end-to-end and two append-only), plus the negative SnapVerify vector in Appendix D.22, are published byte-for-byte as raae-v1-vectors.json in the draft repository.¶
The vectors are organized by purpose:¶
Appendix D.1 shows one complete HKDF-SHA-256 trace, including the commitment KDF inputs. Use it to debug framing, payload_info construction, commitment derivation, segment AAD, accumulator contribution, and snapshot computation.¶
Appendix D.3 exercises the KDF combiner in isolation and demonstrates encode() injectivity, including the empty-sequence and same-octets-different-grouping cases. The 38 combiner vectors are published as raae-v1-combiner-kdf-vectors.json.¶
The single-segment and two-segment vectors exercise the listed AEAD and segment-size combinations with HKDF-SHA-256. Use these to check AEAD code points, segment_max encoding, nonce storage, and finality-bit handling.¶
Appendix D.15 covers the TurboSHAKE-256 KDF suite and its 64-octet Nh outputs.¶
Appendix D.16 shows a segment rewrite and the corresponding accumulator and snapshot update.¶
Appendix D.17 covers AES-256-GCM-SIV in derived nonce mode, where the nonce is recomputed rather than stored.¶
Appendix D.18 shows epoch_length = 0, the finest rotation, and exposes per-segment epoch_key and segment_key values. The AEGIS-256 blocks exercise the opposite endpoint, the flat key at epoch_length = 63.¶
Appendix D.19 exposes the component values of the optional plaintext-bound nonce construction, which no end-to-end vector can reach.¶
Appendix D.2 pins the commitment's G input at its empty default, which equals the Appendix D.1 commitment, and at a nonempty value.¶
All vectors use empty per-segment A_i. As specified in [I-D.sullivan-cfrg-raae], an empty A_i is omitted from the encoding rather than encoded as a zero-length fourth element.¶
Appendix D.22 tampers a present segment tag without recomputing the snapshot. SnapVerify recomputes the accumulator, hence the snapshot, from the present tags and rejects the stored snapshot value.¶
The JSON file is the complete corpus for automated tests. The text below is intended for debugging and review.¶
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
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
00010002000100010000010000011000
20040404040404040404040404040404
04040404040404040404040404040404
04000000020020
commitment = HKDF-Expand(prk, expand_info, 32):
7706d3f4915f89991e9ed1028bca9a53
1645cc94f640b9f1078da70ff67222bd
¶
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
¶
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, produce distinct framed inputs and therefore distinct outputs. The full set of 38 combiner vectors, covering both KDF classes and every SEAL label, 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.¶
The first pair frames the same five info octets 01 02 03 04 05 two ways. Because encode length-prefixes each element, the two expand_info values differ, so the outputs 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",
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
¶
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=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
¶
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
tag (16 octets):
4be0f9482fe889b5875a0abf520ecac8
contrib (32 octets):
88dc23a7bf62deece26e9701d0c45b01
7614863581f6bb061353d362fae86004
accumulator (32 octets):
88dc23a7bf62deece26e9701d0c45b01
7614863581f6bb061353d362fae86004
snap_acc (32 octets):
66958e1b459d872ee1cb648f38998faf
822954c178d8a5b45866266e33abf563
¶
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
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
¶
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=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
¶
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):
75a66ad40b2f7dbea1d72669
tag (16 octets):
a0ff8815a35577d0ed0d5b1483f89131
contrib (32 octets):
7450c8653dc69f6d0601c1cf6f14abff
4254241d31ddd1200ecd9229ecd9ded2
accumulator (32 octets):
7450c8653dc69f6d0601c1cf6f14abff
4254241d31ddd1200ecd9229ecd9ded2
snap_acc (32 octets):
8ac245e01ffded4c18ad1e45b2a0e652
307d4704714c0fe59636eb2ea7dd867f
¶
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
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
¶
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
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
¶
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=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
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
¶
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
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
9852c4fa47436a2adeb44f40023c4e01
accumulator (32 octets):
cebd809a825f76dedd8caf9c0ac4fce8
d509414f93422c4e789a25b09237b55c
snap_acc (32 octets):
7b448954d1af6e545b0dfba7ea33b1a7
e9709331559b9ab8fd4ac8a9a912953a
¶
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
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
¶
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):
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
¶
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.¶
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):
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
¶
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=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
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
¶
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. 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.¶
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
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
Rewrite of segment 0:
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
¶
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. 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.¶
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
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:
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
¶
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.¶
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)
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
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
¶
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 the only way to check 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 both blocks bind the plaintext "Hello, SEAL!". These component vectors are printed in this appendix only and are not part of the end-to-end corpus.¶
The payload schedule is that of Appendix D.1.¶
plaintext (12 octets):
48656c6c6f2c205345414c21
random_input (12 octets):
0f0f0f0f0f0f0f0f0f0f0f0f
pt_digest (32 octets):
e66fec4cada0ccdb73930622ef393d5b
a05fb73bdd81205a9f828f75e85ded81
pt_hash (32 octets):
072fa800d5069a226a7322c5b3fb704f
8564fd2075dd9de6de274a2b5645faaa
nonce_ctx (56 octets):
000a5345414c2d52572d763100080000
0000000000000020072fa800d5069a22
6a7322c5b3fb704f8564fd2075dd9de6
de274a2b5645faaa
nonce (12 octets):
c80d12010cce5b67d9e1fcbb
¶
The payload schedule is that of the single-segment TurboSHAKE-256 block (Appendix D.15). The segment index is 1, visible in the second framed element of nonce_ctx.¶
plaintext (12 octets):
48656c6c6f2c205345414c21
random_input (12 octets):
0f0f0f0f0f0f0f0f0f0f0f0f
pt_digest (64 octets):
f809e1b9b0e28d0fd1dce5cf9e4aae59
fde2b08a551c311b621323a5d2f3c78d
e55c22edcdd091231c4509849acf592a
85ab446dfcff4fac008194e6ef59d9b4
pt_hash (64 octets):
ad6a35f94960ee1004391bdfaee16149
ef56bca4fd5b98abfec56fdb80752624
ade63d71f3b15e9aa5dd9fb0ffa8e533
f7ef5caa7620432cf1c1bf7d97611536
nonce_ctx (88 octets):
000a5345414c2d52572d763100080000
0000000000010040ad6a35f94960ee10
04391bdfaee16149ef56bca4fd5b98ab
fec56fdb80752624ade63d71f3b15e9a
a5dd9fb0ffa8e533f7ef5caa7620432c
f1c1bf7d97611536
nonce (12 octets):
f263e270233c57b229b427b1
¶
These vectors exercise the digest transcript ([I-D.sullivan-cfrg-raae]) 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].¶
CEK (32 octets):
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
salt (32 octets):
04040404040404040404040404040404
04040404040404040404040404040404
commitment (32 octets):
8bf5df9f63f2955bdc1a376cd3199a05
3653fb26ecc266ccbcfb56203ae54307
snap_key (32 octets):
bee28b282450dd38c54bdfa40d98dc9a
df5e2af49cf8c99b4c88b6daf7f08664
¶
Segment 0 is non-final with 16 specified ciphertext octets, segment 1 is final with 8. The leaf is the ciphertext digest LH(ct_i) concatenated with the segment's AEAD tag. Here the tag is a specified component input. The tag binds the segment's nonce, index, and finality ([I-D.sullivan-cfrg-raae]).¶
ct_0 (16 octets):
000102030405060708090a0b0c0d0e0f
tag_0 (16 octets):
202122232425262728292a2b2c2d2e2f
leaf(0)=LH(ct_0)||tag_0 (48 octets):
b726af81e3cadd5c7a486e6a3becc39b
1c5628da64e8145161124bc172a4ad64
202122232425262728292a2b2c2d2e2f
ct_1 (8 octets):
1011121314151617
tag_1 (16 octets):
303132333435363738393a3b3c3d3e3f
leaf(1)=LH(ct_1)||tag_1 (48 octets):
3c36a934a52861d5b6c1da66ab493edb
41f744771e16750ac409b3bed2758f87
303132333435363738393a3b3c3d3e3f
transcript (32 octets):
6d5521e923b50a171000a66a02d0a10b
06e6f70ac9db713d07900d9775effbbf
¶
Each leaf is leaf(i) = LH(ct_i) || tag_i. The transcript's ikm is [snap_key] and its info elements are [commitment, uint64(2), leaf(0), leaf(1)]. The ordered list binds each leaf's position and the count. The stored snapshot value is the transcript.¶
These vectors exercise the epoch digest tree ([I-D.sullivan-cfrg-raae]) 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 leaves, each leaf(i) = LH(ct_i) || tag_i:¶
leaf(0) (48 octets):
b726af81e3cadd5c7a486e6a3becc39b
1c5628da64e8145161124bc172a4ad64
202122232425262728292a2b2c2d2e2f
leaf(1) (48 octets):
3c36a934a52861d5b6c1da66ab493edb
41f744771e16750ac409b3bed2758f87
303132333435363738393a3b3c3d3e3f
leaf(2) (48 octets):
2c8e6a5e8019913bebc27623854e35e3
2bcd1ef439372403f8492e60d571cad1
404142434445464748494a4b4c4d4e4f
leaf(3) (48 octets):
cbd4067f9d6dc18f7bb5f33675a1b01e
6ce102113947a9dc1045776b8e62d200
505152535455565758595a5b5c5d5e5f
¶
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], [LH(epoch_run(e))], 32) over that epoch's two concatenated leaves:¶
d_0 (32 octets):
ef2971de8e06f175b2d2d349b63de419
fcd454489c34ba8a6ae0343f090fa483
d_1 (32 octets):
6f2cc3911e6338241ed6690e21839354
397a4766fe664c2675488db43b357360
¶
The snapshot is KDF(protocol_id, "snap_epoch_root", [snap_key], [commitment, uint64(4), LH(d_0 || d_1)], 32):¶
snapshot (32 octets):
cf556c9805bfccabe30a8ce718834823
6da5b766587e5d00334cb11ab124d5cf
¶
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 snapshot value 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 snapshot value. 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.¶
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 snapshot value 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 snapshot value 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 snapshot value are regenerated with the rest of the corpus.¶
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.¶
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
¶
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.¶