Skip to content

Normative · Part

Identity, pairing and revocation

A node’s root identity is an ECDSA keypair on P-256, IK, used under the substrate signature profile. IK_pub is the SEC 1 compressed encoding: 33 bytes, leading byte 0x02 or 0x03 (SOP/1 §5.2). An implementation MUST reject an uncompressed or hybrid encoding of a node root public key wherever one is presented, rather than converting it (encoding canonicity).

IK_priv MUST be generated inside a platform secure element — Secure Enclave, Android Keystore with StrongBox, TPM 2.0, or equivalent — where the platform provides one, MUST be marked non-extractable at generation, and MUST NOT be generated in ordinary memory and then imported (SOP/1 §5.2).

All signing MUST be delegated to that element, with exactly one exception: change events signed under a currently-valid event-signing subkey. The scope boundary enumerating what that subkey may and may not sign is exhaustive, and an implementation MUST NOT extend it. Issuing a subkey does not change the node’s key_protection, which continues to describe custody of IK_priv alone, and a verifier MUST NOT read a subkey as a custody downgrade of the node (section 19.2).

A hardware signer MUST verify each signature it produces, inside the element, against the public key, before returning it, and MUST abort on failure (SOP/1 §5.4), at that section’s scope. That obligation binds hardware signers only. It does not reach a software signer, and it therefore does not reach a change event signed under an event-signing subkey.

The condition under which that obligation binds is SOP/1 §5.4’s and is not restated here — it turns on a platform-capability fact that section owns, and a second copy of it here would be free to drift out of agreement with the first. What SSP/1 states is the consequence on its own surface: no SSP/1 field carries the implementation’s declaration of whether its element exposes a conforming primitive, and an implementation MUST NOT add one, because SOP/1 §5.4 makes that declaration a local fact that MUST NOT reach a peer. An element that cannot discharge that clause is not thereby downgraded: its key_protection remains whatever section 19.2 adopts for it.

Only three things ever leave the node: IK_pub (33 bytes), signatures made by the element, and the ephemeral public key used during pairing. IK_priv never does, and the interface deliberately exposes no export operation.

site_id and mesh_id derive from IK_pub, over the 33-byte compressed encoding, as defined in terminology.

19.2. key_protection and attestation_kind are adopted by reference

Section titled “19.2. key_protection and attestation_kind are adopted by reference”

SSP/1 adopts both attributes, with the vocabulary and the verification procedure inherited by normative reference from SOP/1 §5.2 and not restated.

  • The three levels of key_protection — software, hardware_unattested, hardware_attested — and the three values of attestation_kind — platform_key_attestation, app_attestation, none — are those SOP/1 §5.2 defines.
  • What hardware_attested requires a verifier to check, and the rule that a failed, absent, expired, revoked or mismatched attestation downgrades to software and not to hardware_unattested, are SOP/1 §5.2’s.
  • The pinned trust store, the fail-closed treatment of unavailable revocation data, and model-class exclusion are SOP/1 §5.2’s.
  • The policy ordering software < hardware_unattested < hardware_attested, and the requirement that a node which cannot meet a required level is refused rather than admitted lower, are SOP/1 §5.2’s.

Adoption rather than inheritance-in-name-only is the right call because SSP/1 is where the policy that consumes these values lives. Scope section 15.2 is the mesh’s policy mechanism and scope section 15.3 is how roles travel; SOP/1 §5.2 requires policy to be able to demand a minimum level for a given role or scope, and there is no such thing as a role or a scope in SOP/1 to demand it against. A value SOP/1 defines and SSP/1 must act on has to be nameable in SSP/1.

What SSP/1 does not do is restate the level definitions or the verification procedure: a reference cannot drift from its target, and a restatement can.

Key custody is a three-valued ordering, not a binary, and this section imposes no blanket ban on software custody: SOP/1 §5.2’s default minimum is software for every capability except minting membership authorization records, so a production node holding a software root is expected behaviour there, and a flat prohibition here would contradict the specification that defines the levels. Two specifications disagreeing about whether a deployment is conforming is worse than either answer.

The rules:

  • An implementation MUST record the node’s own key_protection and attestation_kind per SOP/1 §5.2, and MUST determine hardware_attested by verification rather than by assertion.
  • An implementation MUST surface the recorded level to the user. Silent downgrade is the failure that matters: a user who believes their key is hardware-protected and is wrong has strictly worse security than one who knows it is not. “Which protection” is a recorded value with three answers, never a build flag with two.
  • An implementation MUST fail closed rather than admit a node below a level policy requires. It MUST NOT fail closed merely because a level is software in the absence of a policy that says so.

SOP/1 §5.2 defines what verifying a level means. SSP/1 has two admission paths and they do not have equal access to evidence, so this section states which path can establish what.

Admission path Evidence available Recorded level
Direct pairing (method = qr or sas) The candidate presents attestation evidence during the ceremony, to a human-present enroller with a live channel to it The SOP/1 §5.2 verified conclusion
Vouched join (method = vouch) None. A voucher attests a key, not a keystore key_protection = software, attestation_kind = none

The vouch row is normative, and its None is deliberate. Section 21.1 binds subject_ik_pub and subject_site_id and says nothing about the subject’s keystore, and it could not usefully say anything: a voucher has no privileged view of a third node’s hardware, so a level carried in a vouch would be a claim relayed by someone with no way to check it. SOP/1 §5.2’s rule that broken evidence must not beat absent evidence applies with more force to evidence that was never possible.

A vouched node that needs a higher level MUST establish it by presenting attestation evidence directly to the recording node on the SOP/1 §5.2 verification path. That this is possible without a second proximity ceremony is what keeps the vouching mechanism useful; that it is required is what stops a quorum of vouchers manufacturing a hardware claim nobody verified.

There is no signed SSP/1-native carrier for a peer’s level, and this specification does not define one. Where SOP/1 is deployed, the signed carrier is the MLS credential-binding transcript, which binds key_protection, attestation_kind and attestation_evidence_hash under the same signature a verifier already checks.

Pairing establishes mutual trust between two nodes. It does not distribute a shared secret, because SSP/1 has no shared-key mechanism: events are signed rather than sealed, and transport confidentiality belongs to the binding. The output of a successful pairing is an entry in each side’s trusted-peer keyring and nothing else.

Enroller E and candidate C exchange offers over any untrusted transport. The offer’s wire encoding is a deterministic CBOR map:

PairingOffer = {
ik_pub : bstr 33 P-256 root identity public key, SEC 1 compressed
ek_pub : bstr 32 X25519 ephemeral public key
nonce : bstr 16 random
site_id : bstr 16
}

Every field is REQUIRED; a receiver MUST reject an offer with missing, extra or mis-sized fields. site_id MUST bind to ik_pub — a receiver recomputes it and rejects a mismatched offer.

ik_pub is 33 bytes and ek_pub is not. ek_pub is the X25519 key-agreement ephemeral, which the signature profile does not reach: the signature suite changed and the key-agreement suite did not.

The offer is not itself a COSE message. It is a transient ceremony artifact, authenticated by the transcript signature below and by the out-of-band comparison, and it is never stored or forwarded — the dividing rule in the registry.

Both sides compute the transcript, with sort meaning lexicographic ordering on raw bytes so that both derive the same value regardless of role:

T = "sovm-mesh-pair-v1"
|| sort(ik_pub_E, ik_pub_C)
|| sort(ek_pub_E, ek_pub_C)
|| sort(nonce_E, nonce_C)

T is 179 bytes. Every component is fixed-width, so the concatenation is injective without length prefixes and no separator discipline is needed.

sort is well-defined only because encoding canonicity pins “the encoded bytes” to a single answer — two peers that disagreed about point encoding would both sort differently and hash differently, and would abort with no indication of which.

Then the X25519 shared secret dh, and the session key:

SK = HKDF-SHA256(ikm = dh, salt = T, info = "pair-session", L = 32)

Each side signs T under IK_priv inside its secure element and verifies the peer’s signature against the offered ik_pub, after point validation. Failure aborts the ceremony with no downgrade path.

Binding the transcript into the salt is what makes the session key specific to this exchange: an attacker replaying a recorded offer cannot arrive at the same SK without also controlling both nonces and both ephemeral keys.

SK has exactly one purpose: deriving the SAS below. SSP/1 has no shared key to transfer, so nothing else is derived from it. On ceremony completion or abort, SK and both ephemeral private keys MUST be zeroized.

Exactly one authenticator is REQUIRED. The key agreement above authenticates the keys to each other; only a human comparison authenticates them to the right peer, which is what defeats a machine-in-the-middle.

QR commitment. BLAKE3(ik_pub || ek_pub || nonce) — an 81-byte pre-image — 32 bytes of output, displayed by one side, scanned by the other, and compared in constant time against the offer received over the transport.

The pre-image is 81 bytes because ik_pub is 33 bytes and ek_pub is not, which is worth stating because a reader who widens both inputs gets an 82-byte pre-image and a ceremony that aborts against a conforming peer for no visible reason. All three components are fixed-width, so injectivity is preserved.

Short authentication string (SAS). Derived from the session key and compared aloud or on screen:

okm = HKDF-SHA256(ikm = SK, salt = "", info = "pair-sas", L = 8)
digits = int_be(okm) mod 10^12
sas = digits, zero-padded to 12 characters, grouped 4-4-4

int_be is big-endian interpretation of the 8 octets as an unsigned integer.

Two details are load-bearing and easy to get wrong:

  • The reduction is explicit. “40 bits, rendered as 12 zero-padded decimal digits” would be impossible to implement as stated — 2⁴⁰ exceeds 10¹² and 12 digits cannot represent the range — so every implementation of such a rule would silently apply a modular reduction its specification never mentioned. Because both sides must display identical digits or the ceremony aborts, an unstated reduction is an interoperability failure, not a formatting detail.
  • The wide input keeps it near-uniform. Reducing 64 bits into 10¹² leaves a modulo bias below 2⁻²⁰ of a digit’s worth, which is negligible. Reducing exactly 40 bits would produce a measurable bias toward low values — the reason the derivation is 8 octets rather than 5.

The resulting strength is log₂(10¹²) ≈ 39.9 bits against a one-shot machine-in-the-middle, which must be committed to a guess before the humans compare.

An implementation MUST accept only a human match/mismatch verdict. It MUST NOT expose any interface that ingests a peer-transmitted SAS value, because a SAS compared by software over the same channel it is meant to authenticate proves nothing.

Both sides run the same machine. Any failure lands in aborted; there are no other exits and no retry-in-place.

State Transition on Next state
idle Offer sent and peer offer received offers_exchanged
idle Ceremony deadline exceeded aborted
offers_exchanged site_id binding and both transcript signatures verify transcript_verified
offers_exchanged Any verification failure aborted
offers_exchanged Ceremony deadline exceeded aborted
transcript_verified Human confirms the QR or SAS comparison authenticated
transcript_verified Human reports mismatch, or ceremony deadline exceeded aborted
authenticated Peer appended to the trusted-peer keyring paired

Rules:

  • A ceremony MUST NOT be resumed after aborted. A retry is a new ceremony with fresh ephemeral keys and nonces — reusing either would let a machine-in-the-middle retry against the same comparison values.

  • Every ceremony terminates. An implementation MUST abort a ceremony that has not reached authenticated within a deadline fixed when the machine enters idle. That deadline spans the whole ceremony and MUST NOT be restarted on a transition: a per-state timer is defeated by a peer that advances the machine one step and then stalls again. idle and offers_exchanged each wait on an event only the peer supplies, so without this bound a peer that opens a ceremony and goes silent pins an X25519 ephemeral secret and a half-built transcript on the other side for as long as it likes — a denial of service on the same pre-authentication input path that section 20.1 already hardens against malformed offers.

    The deadline’s value is implementation-chosen rather than fixed here. Nothing on the wire carries it, the two sides never negotiate it, and each aborts on its own authority, so a normative number would buy no interoperability, while an over-tight one would abort legitimate ceremonies: the duration is gated on a human comparing digits (section 20.2), and human reaction time varies far more than any protocol round trip. What is normative is that a bound exists and covers idle.

  • An implementation MUST NOT surface the out-of-band comparison before reaching transcript_verified, or the human is comparing digits for keys nobody has proven possession of.

  • Zeroization of SK and the ephemeral keys happens on every path out of the machine, paired included.

Each side appends the peer to its trusted-peer keyring:

Field Type Notes
site_id 16 bytes Primary key
ik_pub 33 bytes Peer’s P-256 root identity public key, SEC 1 compressed
key_protection enum Per SOP/1 §5.2, established per section 19.4
attestation_kind enum Per SOP/1 §5.2
paired_at timestamp
method enum qr | sas | vouch
vouched_by string "self" for direct pairing, else the voucher’s site_id
credential_generation uint or absent Seeded from the SOP/1 §5.3 binding transcript verified at ceremony time. Direct pairing only — absent for vouched joins, which have no ceremony transcript
retired_at_hlc unsigned or absent HLC physical component. Absent while the peer is live. Set once, from an accepted succession record, after which the row is verification-only

site_id MUST always be re-derived from the stored ik_pub rather than trusted as transmitted.

A row whose retired_at_hlc is absent is a currently trusted peer. Every predicate elsewhere in SSP/1 that asks whether a peer is trusted — a voucher, a transport binding, an admitted record, a KeyPackage — asks this one, and a retired row does not satisfy it.

A row whose retired_at_hlc is set is verification-only. It exists so that history the retired identity signed below its cutoff stays checkable, and it answers exactly one question: which key verifies a change event authored under its site_id whose hlc_physical_ms is at or below retired_at_hlc. That is kid resolution in the sense of data model §10, which defines a change event’s verification key as IK_pub or the event_pub selected by data model §10.3; so the permitted read includes verifying the retired node’s sovm/ssp-eventkey-v1 chain under the retained ik_pub in order to select event_pub for that HLC. Read more narrowly, retention would preserve history only for nodes that never issued a subkey. An implementation MUST NOT let a verification-only row:

  • resolve a signer for an event whose hlc_physical_ms is above retired_at_hlc;
  • grant an allow in policy;
  • count as voucher standing under section 21.2;
  • resolve a transport binding, so the retired identity cannot open or authenticate a connection;
  • confer membership, or count toward the node count k is derived from in section 21.3, or toward a custodian count;
  • raise a key_protection or attestation_kind level, its own or its successor’s.

Any surface that reads a verification-only row for a purpose other than kid resolution at or below retired_at_hlc is a defect, not a feature.

The freeze. Once retired_at_hlc is set, no control record of any type authored under that site_id is accepted — change events are not control records, and those at or below the cutoff are the permitted read above: no further sovm/ssp-eventkey-v1 generation, no transport binding, no role assignment, no vouch, no second succession record. Each is dropped and surfaced. This is the only normative statement of the freeze; the sections that enforce it cite it here.

The permitted read and the freeze are one rule, not two. Resolving a retired node’s subkey chain under its retained ik_pub (data model §10.3) is safe because, and only because, nothing further is admitted for that site_id. Without the freeze, anyone holding the retired IK_priv — which for a software root is the adversary data model §10.2 describes — would issue a fresh generation with a high prior_revoked_at and forge history at any HLC below the cutoff without ever extracting a subkey. An implementation that has the read without the freeze is not a weaker version of this rule; it is a forgery hole. The freeze without the read is merely over-strict: it loses history rather than authority.

retired_at_hlc is write-once. An implementation MUST NOT clear it and MUST NOT overwrite it, because a row whose cutoff can move is a row whose verification window an attacker can extend.

credential_generation, when present, seeds the G2a high-water mark for this peer: a SOP/1 §5.3.1 verifier MUST initialise its per-site_id mark from this value rather than from first KeyPackage observation. When absent — which is the steady state for vouched joins — the SOP/1 §5.3.1 fallback applies: any transcript from that peer claiming key_protection better than software MUST be rejected until the peer has established a level directly.

Re-adding the same pair is idempotent. Presenting a different ik_pub for an existing site_id MUST hard-fail as an identity conflict. An implementation MUST NOT overwrite: recovery is an explicit, human-initiated re-pair. Silent overwrite here is indistinguishable from a successful impersonation.

Two rules follow from the key encoding and the level columns:

  • The stored ik_pub MUST be the 33-byte compressed encoding. An implementation MUST NOT store an uncompressed key and compress on read, because the stored bytes are what site_id is re-derived from and a conversion on the read path makes the primary key a function of the reader (encoding canonicity).
  • Raising a recorded key_protection MUST require fresh evidence on the SOP/1 §5.2 verification path and MUST NOT be inferred from a peer’s later assertion. Lowering it — on a revoked chain, an expired attestation, or stale revocation data — MUST NOT require a re-pair, because a level that can only ever go up is not fail-closed.

An already-trusted node may vouch a new node into the mesh, so that joining does not require a proximity ceremony with every existing member.

A vouch is a COSE_Sign1 record under the SSP/1 COSE profile:

Vouch = COSE_Sign1(
protected = {
alg : ES256,
content type : "sovm/ssp-vouch-v1",
kid : voucher site_id (16 bytes)
},
payload = deterministic CBOR {
subject_ik_pub : bstr 33,
subject_site_id : bstr 16,
issued_at_s : unsigned seconds
},
external_aad = mesh_id (16 raw bytes)
)

A vouch establishes no key_protection; see section 19.4.

The voucher’s identity travels in the protected kid; a verifier resolves the signing key from its own keyring as section 21.2 step 3 specifies, and that step is what “locally trusted voucher” means. The mesh binds through external_aad, so a vouch is non-transferable across meshes by construction — in any other mesh the signature simply does not verify.

Deterministic CBOR maps are injective by construction, so no length-prefix or separator discipline is needed to keep the encoding injective — and the class of bugs such a discipline guards against cannot arise.

Validity is 86 400 s from issued_at_s, with 300 s future-dating tolerance. The window is short on purpose — a vouch only has to survive long enough to seat the joining node, after which the subject’s ik_pub lives in the keyring and its events verify against the keyring rather than against the vouch.

Vouches travel in the sync exchange’s records member or through the mailbox. They are self-verifying, so the channel needs no additional trust.

A verifier MUST check, in this order:

  1. the record decodes under the COSE profile — content type known, unprotected bucket empty, payload fields present and correctly sized;
  2. subject_site_id binds to subject_ik_pub;
  3. the protected kid resolves in the local keyring as a currently trusted peer (section 20.4) — the voucher is trusted, and trusted for exactly the key the keyring holds. A verification-only row does not satisfy this step, so a retired node’s vouches stop counting from the moment it retires;
  4. the validity window, including future-dating tolerance;
  5. the COSE signature, with external_aad set to the local mesh_id.

The order is normative because each step narrows what the next one has to consider, and checking the signature first would mean verifying a cryptographically valid certificate about a key the verifier has no business accepting. Mesh non-transferability needs no separate step: a vouch from another mesh fails step 5.

Acceptance requires all three, independently:

  1. At least k valid vouches from distinct vouchers. k = 2 where the mesh already has three or more nodes, otherwise k = 1. Self-vouches, duplicate vouchers, and a vouch issued by the subject’s immediate predecessor under a succession record the seating node holds do not count toward the threshold.
  2. An affirmative human confirmation on the seating node. The prompt MUST name both fingerprints — the first 4 bytes of each site_id, upper hex, rendered XXXX-XXXX, as a display aid only. The default MUST be deny.
  3. Where the mesh carries a capability requirement, the joiner’s module-set head MUST cover the requirement head. A joiner that does not cover it is refused, not admitted degraded. A mesh that has minted no requirement record imposes nothing here, and a joiner that has published no module-set record covers only the empty requirement.

Requiring the first two is defense in depth: the quorum defends against one compromised trusted node, and the human confirmation defends against a quorum obtained by compromising several. The third is a different question — not whether the joiner is who it claims, but whether it can do what the mesh requires of a member — and it is asked here because §55.3 says admission is where a tier-3 requirement bites.

The predecessor exclusion is the self-vouch exclusion applied to the one case where two site_ids are not two parties. A succession record says the successor is the same operator on the same device, so a vouch from the predecessor is that operator attesting to itself, and counting it would let a node reach k on one operator’s say-so. The exclusion is enforced by the seating peer, and it can only be enforced there: the seating peer learns the predecessor relationship from the succession record and from nothing else. It therefore does not bite for a peer that seats the successor before the record exists — the ordinary case, since the record is signed at cutover. That window is covered by a different rule with a different enforcer: section 22.1’s cutover precondition, enforced by the upgrading node, which knows which vouches it issued and needs no record to know it. Neither party can enforce the other’s rule, because neither holds the other’s evidence. They are two halves of one rule no single party can enforce, not the same rule stated twice, and neither may be deleted as a duplicate of the other. What remains between them is stated in limitations §40.8.

A node leaves the mesh by one of two outcomes. Removal says this node must stop being a member. Retirement says this identity has been replaced by a successor on the same device, and is defined in section 22.1. Both are revocation, and both are the same three steps; they differ only in step 1.

  1. Under removal, remove the node from the trusted-peer keyring, so its future events fail signature verification at the apply chokepoint. Under retirement, set retired_at_hlc on its row instead, which makes the row verification-only (section 20.4): its events above the cutoff fail the same way, and its events at or below it stay verifiable.
  2. Deny it in policy, so senders stop offering it data.
  3. Where SOP/1 is deployed, execute the corresponding MLS removal so the node does not receive future group secrets.

Step 1 does more than it appears to, because every durable record resolves its signer through the keyring as a currently trusted peer: once the entry is gone — or, under retirement, once it is verification-only — the node’s transport bindings stop resolving — so its live connections fail authentication — and any vouches it issued stop verifying. Under removal that follows from absence. Under retirement the entry is still present, and the same three consequences follow instead from section 20.4’s prohibitions on a verification-only row. One step, one place, and everything downstream of the identity fails closed together.

Revocation is future-facing. It stops the node receiving new data; it cannot retract what the node already holds. See limitations.

That rule governs vouches as well, and it bounds how far step 1 reaches. A vouch that stops verifying stops being usable as evidence from that moment on: it can no longer be counted toward a join that has not yet been accepted. It does not reach backwards into a join that already was. A node already admitted is not re-evaluated against section 21.3’s threshold because one of its vouchers was later removed, and an implementation MUST NOT unseat it on those grounds. What authorizes a seated node is its own keyring entry, for as long as that entry is a currently trusted peer (section 20.4) — a verification-only entry authorizes nothing; seating consumes the vouch. That is what lets section 21.1 bound a vouch’s validity at 86 400 s: a certificate that had to stay verifiable for as long as its subject remains in the mesh could not expire in a day.

The alternative reading is the one to reject explicitly. Making admission continuously re-derivable from the current vouch set would drop every node the removed voucher had vouched below k, then every node those had vouched, and so on, so a single removal could strand an arbitrary fraction of a fleet. Step 1’s “fails closed together” is scoped to the removed node’s own identity — its events, its transport bindings, and any vouch it issued that no join has yet consumed — never to third parties it vouched in the past.

This settles only what a removal does by itself. It does not speak to whether an authority may deliberately re-run admission for a node already seated; revisiting a seating on purpose is an administrative act, and SSP/1 neither grants nor withholds it here. What is ruled out is re-evaluation as an automatic consequence of an unrelated removal.

SSP/1 defines no wire message announcing a revocation. A peer enforces a removal only once it has independently received the policy change, and the protocol does not bound that window. See limitations.

Retirement exists for one event: a node’s identity is replaced by a successor on the same device, most often because the platform has gained a secure element. Section 19.1 forbids generating IK_priv in ordinary memory and importing it into an element, so the key cannot be moved; the node mints a new identity, seats it through section 21.3 like any other joiner, and retires the old one. Removal would serve, and would cost every event the old identity ever signed: at the apply chokepoint an unverifiable event is dropped, so every peer that joins afterwards would silently never receive that history. Retirement keeps the row verification-only instead, bounded by a cutoff the retiring identity pins itself.

Retirement requires destruction. Retirement is available only where the predecessor’s private key is destroyed at cutover, immediately after it signs the succession record. Every other path into section 22 is removal. In particular the snapshot-restore case of data model §11.2 is removal: a restore rolls back the storage around an identity key rather than the key, so the predecessor’s key survives, and the hazard that section names — re-stamping HLCs below the restored node’s own high-water mark — lies below any cutoff, which is precisely the region a retained row still resolves. A cutoff bounds what a key signs next only once that key is gone; a surviving key signs below it at will.

The cutover precondition. The retiring node MUST NOT sign a succession record until at least k peers hold a keyring row for the successor, counting only peers that seated it independently of the retiring identity — on vouches that do not include one the retiring identity issued — where k is section 21.3’s threshold. Below k, destroying the key strands every peer that has not yet seated the successor, permanently, because the only identity that could have vouched it to them no longer exists; at k, those peers seat it by the ordinary path. The precondition is enforced by the upgrading node, because it is the only party that knows which vouches it issued before any record exists. It is not the same rule as section 21.3’s predecessor exclusion, which is enforced by the seating peer from the succession record: each party holds only its own evidence, so each rule covers the window the other cannot reach, and neither may be deleted as a duplicate of the other.

A succession record is the retiring identity’s own statement that a named successor replaces it, and of the cutoff below which its signatures remain checkable. It is signed by the retiring IK before that key is destroyed, and unilaterally — no quorum, no counter-signature from the successor, no third-party confirmation. The precedent is data model §10.1, where IK alone populates prior_revoked_at: an identity is the only party that can bound its own signing window, and requiring a quorum would make retirement impossible for exactly the node whose key is about to become unavailable.

Succession = COSE_Sign1(
protected = {
alg : ES256,
content type : "sovm/ssp-succession-v1",
kid : predecessor site_id (16 bytes)
},
payload = deterministic CBOR {
successor_ik_pub : bstr 33,
successor_site_id : bstr 16,
retired_at_hlc : unsigned,
issued_at_s : unsigned seconds
},
external_aad = mesh_id (16 raw bytes)
)
Field Rule
kid The predecessor, because the predecessor signs; a verifier resolves the signing key from its own keyring, as for a vouch
successor_ik_pub, successor_site_id Both travel, as in a vouch, and the site_id is checked against the key, never trusted as transmitted
retired_at_hlc The HLC physical component at and below which the predecessor’s events stay verifiable. It bounds events, which are ordered by HLC, so it is not a wall-clock time
issued_at_s When the record was signed. It opens no validity window: the record has no expiry, because it is retained as the justification for a peer’s retention and must outlive any window

The record is not a chained record: it has no generation and no predecessor member, because there is at most one succession per identity — the predecessor’s key is destroyed. Its content type is a distinct registry literal rather than a reuse of an existing one, per data model §10’s domain separation: a verifier decides what a signature authorizes from the protected content type, so a record that retires an identity MUST NOT be confusable with one that does anything else.

A verifier MUST check, in this order:

  1. the record decodes under the SSP/1 COSE profile — content type known, unprotected bucket empty, payload fields present and correctly sized;
  2. successor_site_id re-derives from successor_ik_pub;
  3. the protected kid resolves as a currently trusted peer (section 20.4) — a retired predecessor cannot author a second succession, which is the freeze;
  4. retired_at_hlc does not exceed the verifier’s own wall clock by more than data model §11.1’s 300 000 ms, so a retiring node cannot pin its cutoff far ahead and grant itself a window in which an extracted key still verifies;
  5. the COSE signature, with external_aad set to the local mesh_id.

The order is normative for the same reason as section 21.2’s: each step narrows what the next must consider, and a signature checked first would be a valid signature about a retirement the verifier has no business recording.

What acceptance does. Accepting the record sets retired_at_hlc on the predecessor’s row and performs section 22’s steps 2 and 3 unchanged. It does not seat the successor, which happens only through section 21.3, and it carries nothing across: the successor’s key_protection is established from its own evidence per section 19.4. A record naming a successor the mesh never seats leaves a retired row and no new member — the mesh loses a node and keeps its history, which is the correct outcome.

Forks. Two distinct succession records under one predecessor kid are a fork, and are treated as detectable evidence, never a silent divergence. A verifier that holds both before accepting either MUST retire on neither and MUST surface the fork; one that receives a second after accepting the first rejects it at step 3, MUST surface it as a fork, and MUST NOT move the cutoff. Choosing between two records would let whoever holds the key choose the cutoff, the one parameter this section rests on. Because the record is IK-signed, a fork is evidence of IK-level compromise or a node defect, as for an IK-signed subkey fork in data model §10.3.