Messages
This page defines the protocol message taxonomy and the normative wire-format rules for exchanging messages between two paired participants. Pairings include Owner ↔ Helper, Owner ↔ Replica, and Replica ↔ Replica. All protocol messages (except ContactMessage) are carried inside a DeRecMessage envelope.
Message Taxonomy
ContactMessage
ContactMessageContactMessage is the sole protocol message that is not wrapped in DeRecMessage; it is transferred out-of-band (e.g., QR code).
Fields
mlkemEncapsulationKey
eciesPublicKey
Together provide public encryption material to established a trusted session.
channelId
Channel identifier for the pairing session
nonce
Pairing session nonce
transportProtocol
Endpoint and transport to reach the sender
timestamp
Message creation time
Constraints
nonceMUST match the nonce carried in subsequentPairRequestMessageandPairResponseMessagefor the same pairing sessionchannelIdidentifies the channel expected for subsequent protocol messages sent to the contact creatortransportProtocolMUST be used to deliver the firstPairRequestMessage
DeRecMessage Envelope
DeRecMessage EnvelopeAll protocol messages other than ContactMessage are sent as a serialized DeRecMessage.
Fields
protocolVersionMajor
protocolVersionMinor
Protocol major and miner version
sequence
Monotonic message sequence number
channelId
Channel identifier
timestamp
Envelope creation time
message
Encrypted serialized MessageBody
Notes
The
messagefield contains encrypted bytesAfter decryption,
messageMUST deserialize intoDeRecMessage.MessageBodyExactly one inner message type is carried per envelope
PairRequestMessage & PairResponseMessage
PairRequestMessage & PairResponseMessageFirst message sent from pairing initiator to pairing responder after out-of-band ContactMessage transfer.
SenderKind
SenderKindSenderKind identifies the logical role of the sender. Pairings form by matching complementary kinds:
Owner
Helper
ReplicaSource
ReplicaDestination
Owner— initiatesProtectSecret,VerifyShares,Discovery,RecoverSecret, andUnpair. There is no protocol-level distinction between an Owner pairing for the first time and an Owner re-pairing in recovery mode.Helper— accepts the Owner-initiated flows above.ReplicaSource— initiates a replica pair handshake and the initial Secret sync. Once the Destination has received its first Secret, both sides converge to operationally equivalent Owner-identity devices.ReplicaDestination— accepts the inbound replica pair handshake and receives the initial Secret sync. Same operational footing as the Source thereafter.
Each peer's SenderKind is recorded on the channel at pairing time and the protocol enforces flow directionality against it. A Helper attempting to initiate ProtectSecret is rejected with a role-mismatch error. Inbound messages are gated the other way around — a StoreShareRequest is only honored on a channel where this node is the receiver (Helper or ReplicaDestination).
Fields — PairRequestMessage
PairRequestMessagesenderKind
Role of initiator
mlkemCiphertext
ML-KEM ciphertext for responder bootstrap
eciesPublicKey
Initiator ECIES public key
channelId
Channel identifier chosen by initiator
communicationInfo
Application-defined identification info
nonce
Pairing session nonce
parameterRange
Supported operating range
transportProtocol
Endpoint and transport to reach initiator
timestamp
Message creation time
Fields — PairResponseMessage
PairResponseMessageresult
Pairing result
senderKind
Role of responder
communicationInfo
Application-defined identification info
nonce
Pairing session nonce
parameterRange
Supported operating range
timestamp
Message creation time
CommunicationInfo
CommunicationInfoCommunicationInfo carries application-defined identifying metadata about a participant — the kind of human-readable context the receiver can display to help the user identify and confirm the counterparty.
communicationInfoEntries
List of key-value entries. Each entry is one piece of identifying information for the sender.
Each entry's key is an application-defined identifier (e.g. "name", "email", "phone"), and exactly one of stringValue / bytesValue MUST be set. The protocol does not interpret or validate keys or values — the entries are opaque to DeRec.
Applications SHOULD use common keys where possible ("name", "email", "phone", etc.) to improve cross-implementation UX, but no global registry is enforced.
This data MUST NOT be relied upon for authentication. Authentication is handled outside the protocol (in-person confirmation, KYC, application-specific mechanisms). CommunicationInfo is for UI/UX only.
Reserved derec.* namespace
derec.* namespaceThe protocol never inspects keys or values in CommunicationInfo except for entries whose key is under the derec.* namespace, which are reserved for the protocol itself.
Applications SHOULD NOT use keys starting with derec.. The protocol silently strips and re-injects these entries at the wire boundary, so any application-supplied values in this namespace are overwritten.
Currently defined:
derec.replica_id— decimal-encodedu64. Carries the sender's stable replica identity on replica-mode pairing envelopes. Auto-injected on outbound replica pairings and auto-extracted on inbound, populatingChannel.replica_id. Per-pairing-kind validation: replica pairings (ReplicaSource/ReplicaDestination) MUST carry it; non-replica pairings (Owner/Helper) MUST NOT.
PrePairRequestMessage & PrePairResponseMessage
PrePairRequestMessage & PrePairResponseMessageThe PrePair exchange is a plaintext round-trip that fetches the actual ML-KEM and ECIES public keys when the initial ContactMessage used contactMode == HashedKeys — a mode where the ContactMessage carries only a contactBindingHash (48 bytes) instead of the public keys themselves, to fit in size-constrained out-of-band channels like QR codes.
The scanner MUST validate the received keys against the contactBindingHash from the ContactMessage before proceeding to send a regular PairRequestMessage.
Fields — PrePairRequestMessage
PrePairRequestMessagenonce
Echoed from the original ContactMessage.nonce. Mismatches indicate a stale or spoofed request; the receiver MUST refuse to serve the keys.
transportProtocol
Transport endpoint at which the sender expects to receive the PrePairResponseMessage.
timestamp
Message creation time.
Fields — PrePairResponseMessage
PrePairResponseMessageresult
Operation result. On Ok, the two key fields below are present. On any non-Ok status, they MUST be absent.
mlkemEncapsulationKey
Serialized ML-KEM-768 encapsulation key. Present only on Ok.
eciesPublicKey
Serialized ECIES public key. Present only on Ok.
nonce
Echoed from the request for session correlation.
timestamp
Message creation time.
Envelope and security
Both messages are carried inside a DeRecMessage envelope, but the envelope's message field carries the serialized PrePair*Message bytes unencrypted — no shared symmetric key exists at this point in the handshake.
Scanner-side validation on receipt of PrePairResponseMessage:
Confirm
result.status == Ok. On non-Ok, surface a typed error and do not proceed.Recompute
SHA-384(mlkemEncapsulationKey || eciesPublicKey || u64_be(nonce) || u64_be(channelId))and verify it matches the originalContactMessage.contactBindingHash.Only on a match, proceed to construct and send a normal
PairRequestMessage.
The transportProtocol field is observable to a passive network attacker. Because the PrePair envelope is plaintext, the transport endpoint carried in both PrePair messages (and in the HashedKeys-mode ContactMessage itself, if its out-of-band channel is observable) is visible to any observer on the path.
To preserve the security guarantees of HashedKeys mode, the application MUST:
Use an ephemeral, short-lived transport endpoint during pairing — bound to the pairing attempt, not to the long-term identity of either peer.
After pairing completes, swap to a long-term endpoint via
UpdateChannelInfoRequestMessage, and retire the ephemeral pairing endpoint.
InlineKeys mode does not have this constraint because the PairRequestMessage itself is asymmetrically encrypted from the start.
ParameterRange
ParameterRangeFields
minShareSize
maxShareSize
Minimum and maximum accepted share size
minTimeBetweenVerifications
maxTimeBetweenVerifications
Minimum and maximum accepted verification interval
minTimeBetweenShareUpdates
maxTimeBetweenShareUpdates
Minimum and maximum accepted share update interval
minUnresponsiveDeletionTimeout
maxUnresponsiveDeletionTimeout
Minimum and maximum deletion timeout for unresponsive peer
minUnresponsiveDeactivationTimeout
maxUnresponsiveDeactivationTimeout
Minimum and maximum deactivation timeout for unresponsive peer
TransportProtocol
TransportProtocolFields
uri
Transport endpoint
protocol
Transport type
Validation
The library validates every TransportProtocol at the wire-bytes ingress; peers shipping a mismatched scheme are refused.
protocolcarries a discriminant — currently onlyHttps(0).uriMUST start with the scheme matching the declared protocol:Protocol::Httpsrequires the URI to begin withhttps://. Plaintext (http://) and other schemes (ws://, …) are rejected.urilength is capped at 2048 bytes.uriMUST NOT contain control characters — bytes< 0x20or= 0x7F(NUL, embedded newlines, terminal escape codes) are rejected.Unknown
protocoldiscriminants are rejected at the conversion boundary.
Validation failures are reported through the protocol-level error mechanism and rejected at the wire-bytes ingress before any further processing.
UnpairRequestMessage & UnpairResponseMessage
UnpairRequestMessage & UnpairResponseMessageRequests that the other party ceases the DeRec relationship and deletes relationship data, subject to regulations or service agreements.
Fields — UnpairRequestMessage
UnpairRequestMessagememo
Human-readable reason
timestamp
Message creation time
Fields — UnpairResponseMessage
UnpairResponseMessageresult
Operation result
timestamp
Message creation time
StoreShareRequestMessage & StoreShareResponseMessage
StoreShareRequestMessage & StoreShareResponseMessageThis message has two uses, distinguished by shareAlgorithm:
To a Helper (
shareAlgorithm = 0): thesharefield carries one committed share for the Secret at the givenversion. The Helper persists it indexed by(secretId, version, replicaId)and does not introspect the bytes.To a Replica (
shareAlgorithm = 1): thesharefield carries the full encoded Secret payload — a snapshot of the entire Secret at the givenversion. The receiving Replica installs it locally. See theSecretpayload section below for the structure.
Fields — StoreShareRequestMessage
StoreShareRequestMessageshare
Opaque share bytes (committed share for Helpers; encoded Secret for Replicas — see shareAlgorithm)
shareAlgorithm
Share encoding identifier — 0 = CommittedDeRecShare, 1 = full Secret payload
secretId
Identifier of the protected Secret
version
Version of the Secret being distributed
keepList
Versions to retain at the receiver
versionDescription
Optional version label
timestamp
Message creation time
replyTo
Optional transport endpoint to which the receiver should send its response. See replyTo and routing replies.
replicaId
Optional stable per-device identifier of the writing device. See the callout below.
Fields — StoreShareResponseMessage
StoreShareResponseMessageresult
Operation result
secretId
Echoes the Secret identifier from the request
version
Echoes the version from the request
timestamp
Message creation time
replicaId is the stable per-device identifier of the writer. Absent (the default) means a non-replica Owner produced it; present means a Replica device is announcing its identity. Helpers persist replicaId alongside the share so distinct replicas writing the same (secretId, version) coexist instead of overwriting each other. The protocol does not authenticate the claim — conflict resolution is the application's responsibility (see Conflict resolution); the protocol just surfaces the metadata.
Secret payload
Secret payloadWhen shareAlgorithm = 1, the share field carries the serialized Secret message — a snapshot of the entire protected Secret at the given version. Replica receivers decode it and install the result as their local Secret.
helpers
Snapshot of every paired Helper for this Owner identity, including the per-Helper Shared Key, transport endpoint, and channel id. This is what makes any Replica able to talk to those Helpers as the Owner identity.
secrets
The user-facing data the Owner identity is protecting — one entry per logical secret.
replicas
Snapshot of every paired Replica device under the same Owner identity, including the replica-group Shared Key that all Replicas use to communicate among themselves.
owner_replica_id
The replica_id of the device that produced this version of the Secret. Used by receivers to attribute origin and to detect concurrent-write conflicts.
See Secret for the conceptual treatment, including how version increments propagate and how the replicas field carries the group's Shared Key.
CommittedDeRecShare (share algorithm 0)
CommittedDeRecShare (share algorithm 0)When shareAlgorithm = 0 in a StoreShareRequestMessage, the share field carries the serialized CommittedDeRecShare message — one committed share bound to a Merkle-root commitment that lets the requester detect tampering during recovery.
deRecShare
Serialized DeRecShare for this specific Helper. See below.
commitment
Merkle root of the shares distributed in this round. SHA-384 hash. Every Helper receives the same commitment for a given (secretId, version) round.
merklePath
Bottom-up Merkle path from this share's leaf to the root, as a sequence of SiblingHash { isLeft, hash } entries. The isLeft flag records whether the sibling is the left child at each level.
merklePath lets the recovery-time verifier reconstruct the Merkle root from deRecShare alone and cross-check it against commitment. Distinct shares in the same round produce distinct leaves but a common root, so any tampering with a single Helper's stored bytes surfaces as a mismatch.
DeRecShare
DeRecShareDeRecShare is the inner share content wrapped by CommittedDeRecShare.deRecShare.
encryptedSecret
The encoded Secret payload for this (secretId, version) round, encrypted with a per-round AES-256 key.
x
Random 256-bit x coordinate for this Helper's Shamir point. Encoded as two's-complement big-endian bytes.
y
Corresponding y coordinate — y = f(x), where f is the sharing polynomial whose evaluation at 0 is the encryption key for encryptedSecret.
secretId
Identifier of the protected Secret. Serialized inside the share so it is covered by the signature on the enclosing envelope.
version
Version number of the share. A Helper is entitled to ignore any StoreShareRequestMessage whose version is less than or equal to the last one it accepted.
Sharing is done in GF(p) where p is the smallest 256-bit prime. The polynomial degree determines the recovery threshold — t distinct (x, y) pairs are needed to interpolate and reconstruct the key at x = 0.
DeRecSecret
DeRecSecretDeRecSecret is the Owner-side representation of the material distributed to Helpers in one share-algorithm-0 round — the bytes to be protected, the sharing configuration, and the roster of Helpers with the parameters each Helper agreed to at pairing time.
secretData
The bytes to be protected in this round. Encoded verbatim.
creationTime
Timestamp for this snapshot. Used for auditing, version tracking, and observability.
helperThresholdForRecovery
Minimum number of Helpers required to reconstruct the Secret — the t of the threshold sharing scheme.
helperThresholdForConfirmingShareReceipt
Minimum number of Helpers that must acknowledge receipt of a new share version before older versions may be safely deleted.
helpers
Roster of Helpers participating in this share round, each with a HelperSpecificInfo. Length defines the total number of shares.
Changes to secretData, the Helper set, or any threshold produce a new version — the current Secret snapshot is what the next share round distributes.
HelperSpecificInfo
HelperSpecificInfoHelperSpecificInfo is one Helper's entry in DeRecSecret.helpers.
helper
SHA-384 hash of the Helper's public encryption key. Stable identifier for the Helper within the context of this Secret without exposing the raw key.
helperParams
The ParameterRange agreed with this Helper at pairing time — the intersection of both parties' supported ranges. Used to validate that shares stay within negotiated operational constraints.
VerifyShareRequestMessage & VerifyShareResponseMessage
VerifyShareRequestMessage & VerifyShareResponseMessageOwner challenges Helper to prove it still retains a particular share version.
Fields — VerifyShareRequestMessage
VerifyShareRequestMessageversion
Share version being challenged
nonce
Challenge nonce
timestamp
Message creation time
Fields — VerifyShareResponseMessage
VerifyShareResponseMessageresult
Operation result
version
Share version
nonce
Echoed challenge nonce
hash
SHA-384(committedDeRecShare || nonce)
timestamp
Message creation time
GetSecretIdsVersionsRequestMessage & GetSecretIdsVersionsResponseMessage
GetSecretIdsVersionsRequestMessage & GetSecretIdsVersionsResponseMessageUsed during recovery and routine inventory to request the list of secret IDs and stored versions a Helper currently holds for the requester. The replicaId field on each returned VersionEntry is also the surface that lets the requester detect conflicts between sibling Replicas — see Conflict resolution.
Fields — GetSecretIdsVersionsRequestMessage
GetSecretIdsVersionsRequestMessagetimestamp
Message creation time
Fields — GetSecretIdsVersionsResponseMessage
GetSecretIdsVersionsResponseMessageresult
Operation result
secretList
List of secrets and versions
timestamp
Message creation time
Fields — VersionList
VersionListsecretId
Secret identifier
versions
Stored share versions, each as a VersionEntry
Fields — VersionList.VersionEntry
VersionList.VersionEntryversion
Numeric version identifier
versionDescription
Human-readable description supplied by the Owner at share-storage time
replicaId
Optional stable per-device identifier of the replica that produced this version
replicaId on VersionEntry is copied from StoreShareRequestMessage.replicaId at share-storage time. Two distinct replicas can independently produce the same numeric version for the same secretId (each replica observes the current latest version and increments it); the Helper persists both side-by-side, and this field is what lets the requester tell them apart. Absent means a non-replica Owner produced the version. Resolution of duplicates is the application's responsibility — the protocol just surfaces the metadata.
replyTo and routing replies
replyTo and routing repliesEvery request message — StoreShareRequest, VerifyShareRequest, GetSecretIdsVersionsRequest, GetShareRequest, UnpairRequest — carries an optional replyTo: TransportProtocol field. The field exists to handle a routing asymmetry: Helpers learn the Owner's transport endpoint at pair time and never pair separately with Replicas, so a Replica that sends a request to a Helper using the per-Helper Shared Key from the synced Secret cannot rely on the Helper's default routing to reach back.
If
replyTois present, the receiver routes its response to that endpoint and does NOT persist it as a channel-state change.If
replyTois absent, the receiver falls back to the transport endpoint it learned at pair time.
Owner-driven requests can leave replyTo unset and rely on the default routing. Replica-driven requests must populate it with the Replica's own transport, or the response will never reach them. See Helpers know the Owner, not the replicas for the conceptual treatment.
GetShareRequestMessage & GetShareResponseMessage
GetShareRequestMessage & GetShareResponseMessageRequests a specific share by (secretId, shareVersion) from a Helper.
Fields — GetShareRequestMessage
GetShareRequestMessagesecretId
Secret identifier
shareVersion
Requested version
timestamp
Message creation time
Fields — GetShareResponseMessage
GetShareResponseMessageresult
Operation result
committedDeRecShare
Returned share payload
shareAlgorithm
Share encoding identifier
timestamp
Message creation time
UpdateChannelInfoRequestMessage & UpdateChannelInfoResponseMessage
UpdateChannelInfoRequestMessage & UpdateChannelInfoResponseMessageUpdateChannelInfo is a maintenance flow that lets either party of an already-paired channel propagate post-pairing changes to their CommunicationInfo and/or TransportProtocol without re-pairing. Both fields on the request are optional and independent, but at least one MUST be present.
Unlike every other Owner-initiated flow, UpdateChannelInfo is symmetric — either party may initiate on any paired channel (Owner ↔ Helper, Owner ↔ Replica, Replica ↔ Replica).
Fields — UpdateChannelInfoRequestMessage
UpdateChannelInfoRequestMessagecommunicationInfo
Updated communication info for the sender, or absent to leave unchanged. Presence — including with an empty entries list — instructs the receiver to replace its stored map for this channel with the supplied one. Absence leaves the stored map untouched.
transportProtocol
Updated transport endpoint for the sender, or absent to leave unchanged. Presence instructs the receiver to route the response and all subsequent messages on this channel to the new endpoint.
timestamp
Message creation time.
Replacement semantics for communicationInfo: updates, additions, and deletions are all expressed by the difference between the receiver's current stored map and the supplied one. There is no per-entry delta payload.
Fields — UpdateChannelInfoResponseMessage
UpdateChannelInfoResponseMessageresult
Operation result. On Ok, the receiver has persisted the supplied updates. On any non-Ok status, the receiver's state is unchanged.
timestamp
Message creation time.
Endpoint changeover discipline
When transportProtocol is updated, the receiver routes both its UpdateChannelInfoResponseMessage and every subsequent message on this channel to the new endpoint. This creates a hard dependency the initiator's application MUST satisfy:
Bring up the new endpoint and start listening on it before issuing the request.
Keep the old endpoint operational during the changeover — peers that have not yet processed the update still route to the old address, and in-flight messages from any peer may also target it.
Retire the old endpoint only after every targeted peer has responded (or been observed to route to the new endpoint), plus a grace window for in-flight traffic.
Failing to keep both endpoints reachable through the changeover window will cause messages to be lost. This is the highest-risk failure mode of the flow.
ErrorResponseMessage
ErrorResponseMessageSent by a receiver who receives a bad message back to the sender.
Fields
result
Error result
timestamp
Message creation time
DeRecResult
DeRecResultAll response messages carry a DeRecResult with a status and optional human-readable memo.
Fields
status
Result status
memo
Optional message
Last updated