HyperNet Relay ChatDocumentation
stable / Version 0.1.0Project website
Reference library
Protocol and implementation
On this page
  1. Credential and application bytes
  2. Durable endpoint lifecycle
  3. Membership, ordering and recovery
  4. Retention and integration checks
  5. Client contact verification and protected archives

Persistent encrypted conversations#

HRC uses OpenMLS 0.9.1, RustCrypto 0.6.0 and the OpenMLS SQLite provider 0.3.0 for confidential direct conversations and private groups. The ciphersuite is MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519. Each enrolled endpoint has an independent Ed25519 MLS key. MLS secrets and application records reside in the same guarded SQLCipher store. The account recovery key and the HRC endpoint signing key are separate credentials.

Credential and application bytes#

The BasicCredential identity is deterministic HRC CBOR encoding of ["HRC MLS BasicCredential", 1, {room, actor, mlsKey}]. room includes the home network, incarnation, service and room identifiers. actor is the complete HRC EndpointActor: origin/account, endpoint, security and enrolment generations, and endpoint-binding digest. mlsKey is the exact 32-byte MLS signature public key. All names and binary encodings follow the protocol's camelCase structs. Nicknames never enter credential verification. The credential digest is SHA-256 of these exact bytes.

An ApprovedRoster comes from current authenticated HRC endpoint registrations and room rules. It is not a new trust source: constructing or deserialising one does not authorise a member. Enrolling an MLS public key requires an HRC action signed by the current enrolled endpoint, with its account/generation binding. The crypto boundary requires unique endpoints and keys, at most 64 leaves, and an exact match between every approved credential/key and the actual MLS leaves. An account operator and represented identity remain in the signed Action rather than becoming transferable device keys.

An encrypted Action body is {group, epoch, ciphertext, parent, causation}. Ciphertext is a CBOR byte string. EncryptionContext::from_action and action provide the shared conversion. MLS authenticated data is the canonical encoding of ["HRC MLS application", 1, EncryptionContext]. The context includes home, room, operation ID, complete actor, operation, expected revisions, approval ID, agreement, group, epoch, parent and causation. It excludes ciphertext to avoid a circular hash. The complete signed Action subsequently binds that exact ciphertext. The context is bounded to 8 KiB, application frames to 64 KiB, and membership objects to 256 KiB. There is no alternate UI encoding. Ordered encrypted events retain the complete EncryptionContext and ciphertext; the client recomputes the original Action digest before use. Display plaintext never replaces an authenticated event body.

The encrypted plaintext is canonical ["HRC MLS plaintext", 1, ApplicationMessage], where ApplicationMessage contains text and stable account mention spans. Text limits apply to the decoded text, not a JSON representation. Malformed canonical encoding, invalid UTF-8, invalid grapheme boundaries and excess mention counts produce a bounded rejection. Causation and parent remain authenticated metadata; ordinary unselected @text never gains mention authority.

Durable endpoint lifecycle#

initialize runs before admission, under the store's exclusive ownership. The pinned upstream provider runs its migrations on an empty in-memory connection; the resulting schema is then created in one guarded HRC transaction. This temporary connection contains no endpoint keys, messages or live MLS state. HRC crypto schema version 3 is accepted. A guarded migration from version 2 adds an exact operation binding to pending membership transitions without replacing their commit or Welcome bytes. Legacy pending transitions remain unbound until explicit preparation attaches the matching operation. Unknown versions refuse startup. This migration does not change the pinned MLS provider or its schema; a provider upgrade requires its own qualified migration. All live provider calls borrow the HRC transaction's connection.

Device::provision generates and persists an independent MLS signing key. open checks the persisted actor binding. publish_key_package creates one-use packages, with at most 64 unused records per profile. New group creation persists the exact initial GroupInfo for retries. Joining through a Welcome atomically consumes its package, checks the complete approved roster, and records group identity, epoch and initial home cursor. Any failed check rolls back package consumption. Repeating an exact successful join returns its durable receipt. Creation, successful transition acceptance and Welcome admission offer transaction-local callbacks: the client stores the corresponding signed roster in the same transaction as the MLS epoch. New endpoints receive future messages only; no history keys are imported.

Each operation constructs its provider and loads the group inside the database transaction. No mutable group survives the transaction. A cryptographic failure therefore rolls back all tentative writes and discards the in-memory state.

prepare_message validates text and current roster, advances sender secrets, constructs ciphertext, and invokes a local finalizer to sign the exact Action. The transaction-local finalizer must perform no network I/O or external effects. The shared client uses it for the ordinary exact-byte outbox; a service bot also joins its trigger cursor and durable reply plan. A callback failure rolls back all three state changes. State, plaintext local copy, ciphertext, Action and operation identity commit before the method returns. A retry returns those exact bytes. A changed input under the same operation conflicts. resolve_outbox requires an authenticated durable home outcome; a timeout is not rejection. Only one unresolved send per group is allowed, including approval candidates.

receive_with couples MLS receive state, a valid local message or bounded rejection, cursor movement, and the caller's archive/index updates. The caller first verifies the home event, action, ordering, membership and history access. It supplies the last consumed sequence and the next relevant ordered sequence; unrelated intervening events must already have been accounted for. Duplicate events return the saved outcome. An archive callback failure rolls everything back. Invalid authenticated UTF-8 or text consumes the generation and records InvalidContent without retaining the invalid plaintext. Invalid cryptography first rolls back MLS changes, then separately records the authenticated home position and rejection together with the archive callback. Failure of this second callback rolls back the rejected cursor too. Diagnostics contain no message transcript.

An owner's preview decrypts an exact approval candidate once and saves its verified result privately. decide_preview changes only that immutable record; the domain must check current owner authority and expiry. Matching committed delivery uses the saved result without decrypting again. A rejected candidate does not rewind a ratchet. At most one unresolved preview per sender/group is stored. Approval IDs and all delegated metadata are already bound by the Action.

Membership, ordering and recovery#

prepare_transition creates an add, remove or self-update commit and keeps it pending. Its transaction-local finalizer lets the client persist that change, the exact signed HRC Action and its outbox identity together. Initial group creation uses the same atomic boundary. Current inspection, room revision and retry-window preparation finish before either local MLS write. A signing, serialization or outbox failure rolls back the candidate too.

A verified rejection, received immediately or resolved after reconnect, settles the outbox and only its exact bound membership candidate in one transaction. Another operation's rejection cannot clear a newer candidate. An abandoned unknown membership operation still blocks replacement work in that room until its outcome is resolved; abandonment is not proof that no effect occurred.

Public handshakes are required; external joins, external senders, PSKs and unsupported proposal families are rejected. The home uses create_public_in and accept_public_in inside its authoritative room transaction to verify public GroupInfo, signatures, expected epoch and the exact next roster. accept_public_in also binds the actual MLS commit signer to the authenticated HRC Action's endpoint actor; forwarding someone else's commit cannot relabel it. The author merges only the home-selected winner; a competing local candidate is discarded before processing that winner. Welcome bytes become available only after the exact winning transition is durable. Durable transition receipts make an interrupted acceptance retryable. reject_transition clears only a candidate that the home has durably rejected.

Public-group validation cannot verify the private confirmation value. The home must keep its removal/read-withdrawal send barrier until an eligible endpoint durably verifies the exact epoch/commit and supplies its authenticated receipt. Every other endpoint independently verifies before using the epoch. An invalid membership transition freezes the endpoint at its existing cursor and epoch; it is never skipped into a successor epoch. Recovery creates a new group identity and current authorised roster. It never restores an old sender snapshot or keys held by a removed endpoint.

The ordered profile retains zero old epochs, zero out-of-order generation keys, and permits a maximum forward distance of eight generations. OpenMLS compares the incoming generation with the next ratchet generation: generation 8 from a fresh head 0 succeeds, while generation 9 requires resynchronisation. Eight abandoned local sends also stop further sends until an authorised epoch update. A profile freezes after eight invalid-cryptography gaps, or immediately when a forward-distance failure requires resynchronisation. Senders require an epoch update after 4,096 local application sends. The UI must expose this as maintenance requiring current home permission; an old grant is not renewed by the crypto layer. Membership changes already produce updates.

The provider settings and failure behaviour follow the pinned upstream ratchet implementation, group configuration, message processing, and SQLite provider. The numerical budgets are HRC policy, not claimed MLS defaults.

Retention and integration checks#

Outbox, delivery and approval tables each admit at most 10,000 records; physical storage remains subject to the SQLCipher quota. prune_in shares the caller's archive/index expiry transaction. It deletes consumed delivery/transition records and resolved outbox/approval records for authenticated closed operation windows. Unknown outcomes and unresolved previews remain available for explicit resolution. Permanent closed-window tombstones prevent re-encryption after an outbox record is removed. An old cursor beyond retained records requires explicit resynchronisation. Retention permission and expiry dates belong to HRC policy.

The dedicated Crypto tests use actual SQLCipher and real two/three-member MLS groups. They cover persistence/restart, exact ciphertext retry, transaction rollback, invalid text/cryptography, one-pass approvals, bounded abandonment, forward-distance 8/9, competing commits, Welcome failure and one-use consumption, credential substitution, new-member history exclusion, removal, public-home validation without member secrets, frozen transitions and retention. Real HyperNet admission, endpoint registration, home removal barriers and interface flows are separate integration gates; these crypto tests do not replace them.

Client contact verification and protected archives#

The client replays the public account continuity chain against the signed original registration, authorized surviving controller actions, owner recovery proofs, replacement-key proofs and the home-signed challenges. Before first use, the user explicitly accepts the displayed anchor digest. A legacy service-only anchor is labelled as such and also requires deliberate acceptance. Pinned anchors cannot be silently replaced; extensions must contain the previously retained head. An endpoint-signed MLS key enrolment must match the pinned endpoint and generation. Delegated leaves additionally carry the owner's exact signed grant-creation scope. The home can restrict current access but cannot fabricate a broader signed scope or substitute an MLS key using only its own signature.

Contact inspection requests at most four continuity records per page and retains the existing 1,024-record chain limit. Record indices must be contiguous after the requested cursor. A continuation names the last returned record and requires a nonempty page with room for another record. Short pages remain valid when the response byte limit applies. Each continuation consumes the remaining record budget, so one chain inspection uses at most 1,024 requests. An unchanged retained chain can finish with an empty terminal page. Malformed pagination fails before the fetched chain can update stored contacts or trust.

Historical roster verification may request one exact retained transition or the endpoint's selected unexpired Welcome as evidence. Current shared-room authority still governs that read. A past-only grant may inspect locally decrypted retained content without publishing a live epoch-ready receipt. Required removals and history windows excluding the present prohibit ready/send admission. A missing future transition is an explicit resynchronisation result.

An accepted membership transition changes the room revision and invalidates older signed history cursors. Encrypted catch-up handles an exact CursorContext response by discarding only the history cursor and reading again under current authority. It permits one restart within its existing eight history-read attempts; another context change remains an explicit failure. This does not rewind the MLS epoch, ratchet or transition cursor, discard pending commits, or erase retained plaintext. Replayed transitions and messages keep their existing ordering and deduplication checks. Current presentation and search apply the latest history window and ignore policy to retained content. A denied or expired access decision continues to invalidate the current room cache.

Authenticated events, decrypted application content, and normalized local FTS entries share the encrypted endpoint store. Removing an event cascades to its plaintext/index rows and its consumed crypto delivery copy. Terminal outbox and approval copies expire when their authenticated retry window is closed under the client retention policy. Pending or explicitly abandoned unknown operations and unresolved approval previews remain bounded, inspectable records until resolution; expiry never rewinds a sender ratchet or invents a rejection. The ordinary and crypto outboxes settle accepted/rejected outcomes in one transaction. An accepted CryptoEpochReady also records the exact group, epoch and commit readiness marker in that transaction, including outcomes recovered by exact resolution. A temporary refusal preserves the pending acknowledgement; a verified absence permits only its original bytes to be retried. Membership commit responses are returned before this separate synchronisation/readiness operation, so a later readiness failure cannot replace an already committed membership outcome.

The local client checks current membership, grant history bounds and recipient filters before online encrypted search/context. It never requests server plaintext search. Explicit offline archive search is marked stale. These local tests do not establish qualification of a native HyperNet confidential conversation; that is a separate integration gate.

HyperNet Relay Chat / DocumentationBack to top

Search documentation

Press Escape to close

Loading search...

    Reading appearance

    Choose colours, surfaces and text independently. These settings are saved for this documentation site in this browser.

    Flat surfaces remove decorative materials. Typography is a separate choice. Increased contrast uses the Contrast palette. System forced colours always take precedence.