Normative · Part
Identity, pairing and revocation
19. Root identity
Section titled “19. Root identity”19.1. Suite and encoding
Section titled “19.1. Suite and encoding”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 ofattestation_kind—platform_key_attestation,app_attestation,none— are those SOP/1 §5.2 defines. - What
hardware_attestedrequires a verifier to check, and the rule that a failed, absent, expired, revoked or mismatched attestation downgrades tosoftwareand not tohardware_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.
19.3. Custody tiers
Section titled “19.3. Custody tiers”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_protectionandattestation_kindper SOP/1 §5.2, and MUST determinehardware_attestedby 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
softwarein the absence of a policy that says so.
19.4. How a level is established
Section titled “19.4. How a level is established”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.
20. Pairing
Section titled “20. Pairing”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.
20.1. Key agreement
Section titled “20.1. Key agreement”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.
20.2. Out-of-band authentication
Section titled “20.2. Out-of-band authentication”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^12sas = digits, zero-padded to 12 characters, grouped 4-4-4int_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.
20.3. Ceremony state machine
Section titled “20.3. Ceremony state machine”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
authenticatedwithin a deadline fixed when the machine entersidle. 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.idleandoffers_exchangedeach 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
SKand the ephemeral keys happens on every path out of the machine,pairedincluded.
20.4. Persistence
Section titled “20.4. Persistence”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_msis aboveretired_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_protectionorattestation_kindlevel, 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_pubMUST be the 33-byte compressed encoding. An implementation MUST NOT store an uncompressed key and compress on read, because the stored bytes are whatsite_idis 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_protectionMUST 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.
21. Vouching
Section titled “21. Vouching”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.
21.1. Vouch certificate
Section titled “21.1. Vouch certificate”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.
21.2. Verification order
Section titled “21.2. Verification order”A verifier MUST check, in this order:
- the record decodes under the COSE profile — content type known, unprotected bucket empty, payload fields present and correctly sized;
subject_site_idbinds tosubject_ik_pub;- the protected
kidresolves 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; - the validity window, including future-dating tolerance;
- the COSE signature, with
external_aadset to the localmesh_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.
21.3. Join acceptance
Section titled “21.3. Join acceptance”Acceptance requires all three, independently:
- 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.
- An affirmative human confirmation on the seating node. The prompt MUST name both
fingerprints — the first 4 bytes of each
site_id, upper hex, renderedXXXX-XXXX, as a display aid only. The default MUST be deny. - 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.
22. Revocation
Section titled “22. Revocation”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.
- 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_hlcon 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. - Deny it in policy, so senders stop offering it data.
- 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.
22.1. Retirement by succession
Section titled “22.1. Retirement by succession”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:
- the record decodes under the SSP/1 COSE profile — content type known, unprotected bucket empty, payload fields present and correctly sized;
successor_site_idre-derives fromsuccessor_ik_pub;- the protected
kidresolves as a currently trusted peer (section 20.4) — a retired predecessor cannot author a second succession, which is the freeze; retired_at_hlcdoes 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;- the COSE signature, with
external_aadset to the localmesh_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.