Skip to content

Domains, keys, history and offline members

6.1. One MLS group is insufficient for scoped data

Section titled “6.1. One MLS group is insufficient for scoped data”

A fleet-wide MLS group alone gives every member of that group access to secrets exported from that group’s current epoch.

That is incompatible with a fleet in which different nodes have different data scopes.

Therefore SOP/1 defines cryptographic domain groups.

The mesh SHOULD have one MLS control group containing all currently active fleet nodes that are permitted to participate in fleet control.

The control group carries:

  • fleet membership state;
  • capability negotiation;
  • domain-group membership instructions;
  • key-history provisioning;
  • SOP catalog control messages;
  • other small fleet-wide control information.

The control group MUST NOT imply access to every data domain.

Each cryptographic data domain is represented by an MLS group whose membership corresponds to nodes authorized to decrypt that domain.

Example:

flowchart LR
  accTitle: Example cryptographic domain membership
  accDescr: Four nodes distributed across four domains. The laptop belongs to every domain; the phone is in control, health and general but not finance; the home server is in control, finance and general but not health; the kitchen hub is only in the control group.
  CONTROL --- laptop
  CONTROL --- phone
  CONTROL --- server["home-server"]
  CONTROL --- hub["kitchen-hub"]
  HEALTH --- laptop
  HEALTH --- phone
  FINANCE --- laptop
  FINANCE --- server
  GENERAL --- laptop
  GENERAL --- phone
  GENERAL --- server

A cryptographic domain SHOULD usually contain multiple tables with equivalent confidentiality and node-scope policy.

There SHOULD NOT be one MLS group per table unless the table genuinely has a distinct membership set or security boundary.

The initial deployment taxonomy is decided: CONTROL plus a single GENERAL data domain. A further domain (for example HEALTH or FINANCE) is created only when a real node with narrower scope exists — the governing question is “which node must NOT read this,” and until such a node joins the fleet, additional domains are pure operational overhead.

Deferral is cheap by construction: table-to-domain migration does not require PME object rewriting and does not alter the producer-signed ObjectManifest. It issues a new AccessRecord under the target domain, subject to Section 7.6 entitlement, while preserving the original producer provenance. Migration establishes the new managed access boundary going forward; it cannot revoke plaintext or keys previously obtained by nodes under the source domain (Sections 8.6, 19.3). Blind storage nodes require no domain membership at all (Section 16.3).

Externally visible domain identifiers MUST be opaque.

Identifiers such as:

health
finance
location

MUST NOT be exposed to an untrusted relay or object store merely for routing.

Semantic domain names remain protected application metadata.

SSP scope policy is authoritative.

An implementation MUST verify that MLS domain membership does not grant a node broader access than current SSP scope.

A mismatch MUST fail closed.

Each cryptographic domain has an independently random 256-bit application key-encryption key:

K_domain_generation <- CSPRNG(32 bytes)

The KEK is application state. It is not the MLS epoch secret and MUST NOT be defined as a direct MLS exporter value.

MLS’s role is to authenticate domain membership and confidentially distribute the current application KEK to authorized members.

A domain KEK generation changes only on a membership-security transition.

Mandatory rotation triggers are:

  • removal of a decryption member;
  • confirmed or suspected compromise requiring future-data exclusion;
  • explicit emergency/security rotation;
  • admission of a new member under FORWARD_ONLY semantics where the new member must not gain access to objects wrapped under the previous generation.

Routine MLS self-updates, PCS refreshes, proposals, and other commits that do not change the required SOP data-access boundary MUST NOT by themselves rotate the domain KEK.

A FULL_HISTORY addition MAY receive the existing generation because historical access is intentional.

A rotation triggered by a FORWARD_ONLY admission additionally constrains all future rewrapping in the domain; see the entitlement rule in Section 7.6.

The generator of a new KEK generation is unique by construction: the node whose Commit is accepted by the blind sequencer (Section 22.5) for the triggering membership-security transition MUST generate the new domain KEK and MUST distribute it as soon as its Commit becomes canonical. Because Commits are serialized per group, exactly one generator exists per transition, and concurrent divergent KEKs for the same generation cannot be minted by honest members.

If members observe the new MLS epoch but do not receive the corresponding KEK-distribution message within an implementation-defined window (committer crash or removal before distribution), recovery is a fresh security rotation: a new sequenced Commit whose committer mints the next generation. Members MUST NOT independently re-issue a KEK for an existing generation identifier, as concurrent re-issuance would recreate exactly the divergence the sequencer exists to prevent. A generation identifier for which no KEK was ever distributed is recorded as void.

A newly generated domain KEK MUST be distributed only after the corresponding SSP authorization state and MLS domain membership transition are accepted.

The KEK distribution message MUST travel as authenticated confidential MLS application data.

The application payload SHOULD be a COSE-defined key record or deterministic CBOR record protected by MLS; the exact domain-key distribution payload is part of the SOVM COSE profile.

Every SOP data object MUST use a fresh random DEK:

DEK_object <- CSPRNG(32 bytes)
PME(
parquet,
footer_key = DEK_object,
column_keys = DEK_object
)

The DEK SHOULD be created at object formation time.

For an object formed while offline, the DEK MUST be protected locally under node-local encrypted key storage until publication.

At publication, the object’s DEK MUST be carried in an independently replaceable AccessRecord. The AccessRecord is access-control state, not producer provenance.

The record MUST be signed by a site authorized by current SSP policy to issue access for the target domain, then encrypted under the current accepted domain KEK:

COSE_Encrypt0(
key = K_domain_generation,
protected = {
alg = A256GCM,
content_type = sovm/sop-access-v1
},
plaintext = COSE_Sign1_authorized_issuer(
{
access_record_version,
mesh_id,
storage_id,
object_manifest_hash,
key_ref,
opaque_domain_id,
kek_generation,
access_generation,
previous_access_record_hash?,
entitlement_class,
issuer_site_id,
issued_hlc,
DEK_object
}
),
external_aad = {
protocol_label,
mesh_id,
storage_id,
object_manifest_hash,
opaque_domain_id,
kek_generation,
key_ref
}
)

The outer COSE_Encrypt0 is the wrapped-DEK record: the DEK exists only inside its authenticated ciphertext. The inner COSE_Sign1 makes the access grant durably attributable even after transport/session context is gone.

entitlement_class records the Section 8 historical-entitlement mode under which this grant was issued:

entitlement_class = FORWARD_ONLY | FULL_HISTORY | BOUNDED_HISTORY(params)

It states the entitlement basis of the grant, participates in the Section 7.6 entitlement check during rewrapping and generation retirement, and lets an auditor distinguish an intentional historical grant from an erroneous one. It is a record of the granting decision, not an independent access-control input: possession of the applicable KEK generation remains the enforcement mechanism.

issued_hlc provides audit ordering of access history. It is not the anchor of issuing authority. The authority argument is structural: successfully encrypting under K_domain_generation proves the issuer held that domain KEK generation, i.e. was a decryption member of the domain at that generation, and the inner signature binds the grant to a specific SSP identity. Membership-at-generation anchors authority in the domain’s actual security history more strongly than any claimed timestamp could; validators MUST rely on the generation-possession argument and the Section 5.7 authorization chain, and MUST NOT treat issued_hlc as an authorization input.

access_generation is monotonic within the object’s access history where replacement semantics are required. Each encoded access record has:

access_record_hash = BLAKE3(exact COSE_Encrypt0 AccessRecord bytes)

previous_access_record_hash SHOULD reference that exact hash when replacing an earlier current record so access-history divergence is detectable. Multiple concurrent records MAY coexist where they represent intentionally distinct historical entitlements.

The exact CBOR encoding of protected fields and external_aad MUST be deterministic and versioned.

A new nonce MUST be used for every encryption operation under a given KEK.

COSE replaces the bespoke AEAD_Seal(...) wrapper proposed in earlier revisions.

Because the DEK is independent from the domain KEK, a domain rotation does not require rewriting the PME object.

Where historical access policy requires an object to remain decryptable under a new KEK generation, only its small AccessRecord is replaced or augmented. The immutable producer ObjectManifest is copied by reference/hash and is never re-signed for a rewrap.

The PME bytes, producer manifest, and iroh-blobs hash remain unchanged.

Rewrapping is entitlement-aware. An AccessRecord for object O MUST NOT be created under a KEK generation G unless every node that holds, or is entitled to receive, generation G is also entitled to decrypt O under the current historical-access policy.

In particular, while any current decryption member of a domain holds FORWARD_ONLY (or narrower) entitlement, objects predating that member’s admission boundary MUST NOT receive an AccessRecord under any KEK generation that member holds. The older KEK generations covering those objects MUST be retained by fully entitled members for as long as the objects are retained.

A KEK generation may therefore be retired through rewrapping only when every current decryption member of the domain is entitled to every object still wrapped under it. This is tested by Gate G7.

Retirement policy is decided and lazy. A KEK generation is a few dozen bytes of metadata; generations grow with security transitions, not with data, so there is no storage pressure. Implementations MUST NOT run proactive rewrap campaigns. Compaction migrates data forward naturally — every compacted object receives a fresh DEK wrapped under the current generation — so old generations age out as history is rewritten. A generation is retired when no live access record references it and the entitlement condition above holds; a tombstone record of retired generations is kept. Active rewrap-driven retirement is reserved for incident response.

An offline producer SHOULD NOT bind newly formed objects permanently to a possibly stale shared domain KEK.

Instead:

flowchart TD
  accTitle: Offline creation versus publication
  accDescr: While offline a producer forms the encrypted object and protects its key locally. At publication it verifies membership, obtains the current key generation, signs the manifest, creates the access record and publishes the catalog entry. The object bytes are never rewritten.
  subgraph offline["offline object creation"]
    a["PME object + locally protected DEK"]
  end
  subgraph publish["publication"]
    b[verify current membership] --> c[obtain current required KEK generation]
    c --> d[create producer ObjectManifest]
    d --> e[create COSE AccessRecord carrying the wrapped DEK]
    e --> f[publish catalog entry]
  end
  a --> b

This avoids object re-encryption when the MLS group or KEK generation changed while the producer was offline.

7.8. Separate MLS state from retained application keys

Section titled “7.8. Separate MLS state from retained application keys”

An implementation MUST NOT retain obsolete MLS protocol epoch secrets merely to keep old SOP objects readable.

Historical SOP readability is implemented by retained application KEK generations and wrapped object DEKs, protected separately from OpenMLS protocol state.

Retained application KEKs and offline unpublished DEKs MUST be encrypted at rest under node-local key-encryption material.

Where available, the node-local protecting key SHOULD be hardware-backed.

SOP/1 adopts RFC 9052 and RFC 9053 as normative dependencies.

The SOVM COSE profile MUST specify at minimum:

  • allowed COSE message types;
  • allowed algorithms;
  • protected-header requirements;
  • deterministic CBOR rules;
  • kid / key reference semantics;
  • external AAD transcripts;
  • replay/version fields;
  • signature identity binding;
  • unknown-header handling;
  • algorithm downgrade behavior;
  • the shared chained-record schema used by membership authorization records (Section 5.7) and sequencing receipts (Section 22.5).

Unknown critical headers or unsupported required algorithms MUST fail closed.

The initial SOVM COSE profile permits exactly one suite: A256GCM for COSE_Encrypt0 and EdDSA (Ed25519) for COSE_Sign1. A second mandatory-to-implement suite is added only when a concrete platform or KMS interoperability requirement exists; unused algorithm agility is treated as attack surface, not as flexibility.

Adding a node to a domain MLS group establishes current cryptographic membership, but does not by itself grant SOP data access until the application domain KEK is distributed according to SSP policy.

Admission MUST NOT automatically imply access to historical SOP objects.

SOVM defines two initial historical-access modes.

A newly admitted node receives only current and future domain key material.

It cannot decrypt historical objects whose DEKs remain wrapped only under earlier domain KEK generations.

A newly admitted node is provisioned with the historical application KEK generations required by current retention policy.

Provisioning MUST occur through an authenticated encrypted channel available only after the node is admitted.

MLS PrivateMessage in the relevant trusted group is the preferred mechanism.

A future implementation MAY support bounded history such as:

last 30 days
objects after HLC X
retention generation Y onward

The selection is product policy, not an MLS primitive.

Historical application KEKs SHOULD be transferred in compact key-history packages rather than one message per key.

The package payload MUST use deterministic CBOR and MUST be durably authenticated with COSE_Sign1.

The initial package model is:

COSE_Sign1(
{
package_version,
mesh_id,
opaque_domain_id,
history_policy,
generations: [
{ generation, kek, valid_from, valid_until? },
...
],
issuer_site_id,
issued_hlc
}
)

The signed package MUST be transported only inside an authenticated encrypted MLS PrivateMessage to a group whose every recipient is authorized for the included history, or through a recipient-specific confidential channel explicitly defined for this purpose.

A group-wide MLS PrivateMessage MUST NOT be used to grant history to only one member if other current group members have FORWARD_ONLY or narrower historical entitlement.

The package is secret-bearing data and MUST NOT be persisted outside encrypted node storage.

COSE replaces any bespoke signed/encrypted historical-key package format.

A removed node cannot be made to forget historical domain keys it already received.

The system MUST state this limitation explicitly.

9.1. Offline data creation remains permitted

Section titled “9.1. Offline data creation remains permitted”

An authorized node MAY continue collecting and forming local append data while offline.

Offline-first data creation remains normal operation.

9.2. Publication requires current authorization

Section titled “9.2. Publication requires current authorization”

Mesh publication is distinct from local creation.

Before a newly introduced object catalog entry is accepted into current mesh state, the producer MUST establish current authorization for the applicable domain.

A node that missed ordinary MLS Commits but remains a current member MAY continue to hold and publish under the same application KEK generation when no membership-security transition occurred.

If the missed history contains a KEK-rotation transition, the node MUST first catch up sufficiently to obtain the current required KEK generation before publishing newly introduced objects.

Because offline-created objects retain their DEKs under node-local protection until publication, this requires only creation of the current AccessRecord. The PME bytes and producer manifest are not rewritten.

Catalog-entry acceptance is based on current SSP/MLS membership plus applicable KEK-generation policy; it is not tied to routine MLS epoch equality.

A node removed while offline MUST NOT be able to introduce newly published objects into the current domain using an obsolete membership epoch.

After a removal Commit is accepted, peers MUST reject newly introduced new producer manifests or access grants from the removed site unless the node is explicitly re-admitted.

This rule can reject legitimate data created before removal but never published.

That is an intentional fail-closed tradeoff, and it is decided as final for SOP/1. The dominant removal causes are a lost or stolen node, whose unpublished data is untrusted anyway, and planned decommissioning, which is addressed by prevention rather than recovery: the removal ceremony SHOULD include a pre-removal synchronization check that prompts the user to sync a reachable node before removal completes. The residual case is covered by explicit re-admission. A user-mediated quarantine/import flow remains possible product work but is not a protocol requirement.

SOP/1 MUST NOT solve this ambiguity using untrusted wall-clock timestamps.

A removed node may still retain locally readable historical plaintext or keys from the period in which it was authorized.

No protocol can retroactively revoke already learned information.