Part 11 of 14
Key distribution
Every key in Part 9 had to reach the other side. The channel that carries them is itself encrypted, using the bootstrap material derived from the handshake, and it is reliable — a lost key is not something a peer can work around.
That makes this a small reliable protocol running inside an encrypted envelope, and it inherits the problems of both. It needs the acknowledgement and repair of Part 3, but its control records must not be readable or forgeable by anyone outside the association, and its history holds secrets that must be erased the moment they are no longer needed.
Encoding
Wrapping material as a token
key material is prepared for sending or unwrapped on receipt
Once per key exchange.
Material does not travel as bare bytes. It travels inside a generic container — a class name and a list of named properties — which is the shape DDS Security uses for everything it exchanges.
That container is encoded in whatever byte order the surrounding message used. The material nested inside it is always big-endian regardless.
This is the kind of detail that produces an implementation which works perfectly against itself and fails against everyone else. Two implementations on the same architecture agree; the mismatch appears only when one of them runs on a machine of the other order, at which point key exchange fails while everything else works.
Cost
One pass to encode, one to decode, over a fixed number of properties and a bounded material extent. The secrets are copied once, into the storage that owns them.
Routing
Addressing a token
a token message is built or received
Once per token.
A token message names its source, its destination, its class, and carries exactly one token. Each of those four is checked on receipt, and it is worth being explicit about what each check prevents.
Worked example
| Check | What it stops |
|---|---|
| Source is the peer this channel belongs to | material from one peer installed in another's association |
| Destination is this participant | a token observed in transit being replayed at a third party |
| Route is from the remote volatile writer to the local participant | a token arriving on a path that was never established |
| Class is the expected one, and there is exactly one token | a message carrying several tokens, where only the first is validated |
The last row is the one that reads as pedantry and is not. A container holding a list invites an implementation that validates the list's first element and installs all of them, and cardinality is checked here so that a well-formed-looking message with two tokens is refused rather than half-inspected.
Reliability
Keeping a token until acknowledged
a token is sent, and when the peer acknowledges it
Once each per token.
Participant tokens and endpoint tokens share one reliable writer, so they share one sequence space. An unacknowledged token has to stay available for repair, exactly as in Part 3.
Two things differ from an ordinary history. What is retained is a secret, so acknowledgement must erase it rather than merely release it. And the history is keep-all, which means a full history refuses new work — but it must never evict a token that has not been acknowledged, because that token is a key some association is waiting for and nothing else will supply it.
Acknowledge-Token(h, n)
h the token history · n the sequence the peer has acknowledged up to
- for each entry e in h where e.seq ≤ n
- Erase(e.material) // not merely dropped
- Remove(h, e)
Erasing at line 2 rather than at some later collection is what bounds how long a superseded key exists in memory. The acknowledgement is the moment it is known to be unnecessary, and that is the moment it goes.
Transmission
Building a protected record
a token is first sent, and on every repair of it
Once per transmission, including repeats.
A token is sent, possibly several times if the first attempt is lost. Those transmissions carry the same sequence number — the reliability protocol of Part 3 requires it, since a repair must be identifiable as the sample that was asked for.
But each transmission is separately encrypted, and Part 10 requires that each encryption use a fresh nonce. So the two numbers come apart: one semantic sequence, several cryptographic identities.
The record is built directly into the outgoing buffer rather than assembled and then copied. Staging a complete message first would mean the plaintext of a key existing in a second place, and would add a copy to a path that repeats under loss.
Reliability
Reliable control on a protected channel
the token channel announces or requests
As Part 3, on this channel only.
The channel needs the control records of Part 3 — announcements and requests — and they must be protected too. Unprotected control on a protected channel would let anyone request a repair, and repairs carry keys.
The writer also has to distinguish three things that an ordinary writer can blur: the interval it still holds, the subset the reader has acknowledged, and the subset the reader is asking for. It cannot expand any of them into allocated storage, so all three use the fixed 256-position bitmap of Part 3.
The control records travel inside the same three-record envelope as the tokens, so a receiver authenticates the envelope before it learns whether what it contains is a token or a request. That is the same ordering as Part 10's selection step: structure, then authentication, then meaning.
Receipt
Receiving a participant token
a protected participant token arrives
Once per token, plus once per duplicate.
A token crosses several independent boundaries before it may change anything, and the order they are crossed in is the algorithm.
Receive-Participant-Token(a, msg)
a the participant association · msg the arriving protected message
- inner = Authenticate-Envelope(a, msg)
- if inner == error
- return reject // nothing beyond this point sees unverified bytes
- t = Decode-Token-Message(inner)
- if t == error or t is misaddressed
- return reject
- permit = Admit-Material(a, t.seq, t.material)
- if permit == stale
- return Acknowledge(a, t.seq) // a duplicate is still received
- if not Receipt-Window-Has-Room(a, t.seq)
- return reject // do not acknowledge what cannot be tracked
- if Install(a, permit) == error
- return reject // old material still installed; not acknowledged
- return Acknowledge(a, t.seq)
Line 9 acknowledges a duplicate rather than ignoring it. The peer resent because it did not hear the first acknowledgement, and staying silent makes it resend forever.
Lines 10 and 11 refuse before acknowledging. Acknowledging a sequence the receipt window cannot record would tell the peer to stop resending something the receiver cannot prove it has — and the peer will erase its retained copy on the strength of it.
Line 13 does the same for a failed installation. Acknowledgement means the material is installed; sending it because the message arrived would leave an association whose peer believes it has a key it does not.
Receipt
Receiving an endpoint token
a protected endpoint token arrives
Once per token.
Endpoint tokens arrive on the same reader as participant tokens, and the procedure above applies with one addition: the token names an endpoint match, and it may change that match and no other.
So a missing match, an invalid route, a failed session activation, or an exhausted receipt window each cause the same two things — the sequence is not acknowledged, and no other match is touched.
The second half of that is what a shared reader makes easy to get wrong. A failure while handling one match must not leave the reader's receipt state, or any other match's material, changed. Since a rejected token acknowledges nothing, the peer resends it, and a transient failure resolves itself.
Discovery
Secure discovery
secure endpoint announcements are exchanged
As ordinary discovery, once the participant channel is established.
Endpoint announcements themselves can be protected, so that which topics a participant publishes is not visible to observers. Those are reliable discovery endpoints, and they raise a bootstrapping question: which key protects them?
Not the handshake bootstrap material. That protects the token channel only. Secure discovery uses the participant material, which means it becomes readable only after the token channel has delivered that material — establishing an order between the two that is not otherwise obvious.
Two prohibitions follow, and both are about accepting a weaker path. Plaintext input on a secure discovery endpoint is refused rather than treated as ordinary discovery, and unauthenticated control records for it are refused rather than processed. An implementation that accepted either would offer an attacker a way to bypass the protection entirely by simply not using it.
Cost
The same reliability machinery as Part 3, over the announcements of Part 1, inside the envelope of Part 10. Nothing new per sample; the cost is that discovery cannot complete until key distribution has.
Next: teleoperation
A command that must reach a robot once, in time, over a link that may fail, with nothing allocating.
These descriptions follow the implementations in Tin DDS and Retina. Evaluation access and the full algorithm notes are available from engineering@tinverse.com.