OfflineProtocol from @offline-protocol/mesh-sdk. These signatures match v0.27.0. Read the integration guide for prerequisites and complete usage.
Methods on this page
initializeMlsWithSecureStorageisMlsInitializedgetIdentityPublicKeyderiveAddressparseInvitecreateInviteresolveUsernamelocalAddressderiveUserIdFromPublicKeysignDataverifySignaturemlsGenerateKeyPackagemlsGetOrCreateKeyPackagemlsGetPendingKeyPackagesmlsMarkKeyPackageSyncedmlsImportKeyPackagemlsHasSessionhasPendingKeyPackagegetEstablishmentStateestablishSecureSessionrekeySessionmlsCreateSessionmlsJoinSessionmlsEncryptForUsermlsDecryptFromUsermlsDecryptmlsListSessionsmlsDeleteSessionmlsProcessWelcome
initializeMlsWithSecureStorage
Initializes MLS with built-in secure storage. Uses iOS Keychain or Android EncryptedSharedPreferences. Note: This is called automatically bystart() when encryption.enabled is true (default).
You only need to call this manually if you disabled encryption initially and want to enable it later.
throws: Error if initialization fails
isMlsInitialized
Checks if MLS is initialized. returns: True if MLS is ready for usegetIdentityPublicKey
Gets the identity public key (Ed25519, 32 bytes). This is the public key derived from the MLS credential and can be shared with others for identity verification and secure communication. returns: The public key as an array of bytes throws: Error if MLS is not initializedderiveAddress
Derives the canonical self-certifying address of an Ed25519 identity key:off1… (44 characters), the bech32m encoding of
0x01 || SHA-256(publicKey)[0:20].
The address is a hash of the key, so it authenticates itself: a peer
claiming an address is checked by re-deriving it from the key they
present. The same key always yields the same address, and every address
has exactly one valid string form.
Needs no protocol instance: safe to call before create(), e.g. to
verify an invite or QR code.
returns: The derived off1… address
throws: If publicKey is not 32 bytes
parseInvite
Decodes and verifies an invite blob. Needs no protocol instance: safe to call beforecreate(), which is the
whole point: a scanner verifies a QR code before deciding to act on it.
Verification is mandatory and total. The address must be the one its
public key derives to, and any signature present must verify under that
key, so a resolved InviteInfo is always self-certified.
What it does not prove: that the invite came from who you think. An
attacker’s own correctly-signed invite is indistinguishable from a
legitimate stranger’s: only the out-of-band context (this QR was on
this person’s screen) carries that.
returns: The verified invite
throws: If the blob is malformed, the address is not the key’s, or a
signature does not verify. Every case means refuse, not warn.
createInvite
Builds an invite blob for this identity. The result is opaque base64url. Apps own the container; the recommended form is one parameter,<app-scheme>://connect?c=<blob>, so it composes
with an existing scheme and route.
Sign it when the invite may travel without its issuer, a link
forwarded through a third party, because the signature binds the petname
to the key, so a forwarded invite cannot save Alice’s key under the name
“Bob”. Leave it unsigned for a QR shown phone to phone: the physical
channel already authenticates it, and an app that lets the user confirm
the name has made the user the authority over it. Signing costs about 90
characters.
Carries no key package by design (an MLS init key is single-use and a QR
code is static, so pairing them guarantees a collision as soon as two
people scan the same code) and no expiry (a printed QR that stops working
is a bug).
resolveUsername
Returns true if this call started the lookup, false if one was already running; exactly one username_resolved follows either way. Requirestransports.nostr.usernameDiscoveryEnabled. Resolves true if
this call started the lookup and false if it joined one already in
flight. Both mean an answer is coming: exactly one
username_resolved event follows either way, so awaiting that event after
either result is safe.
Every case where no event will ever arrive rejects instead, so a
false can never leave a spinner running forever. The rejection code says
which, and they call for different handling:
InvalidConfiguration: discovery is off (it also requirescoldContactEnabled). Retrying unchanged can never succeed.InvalidState: too many lookups in flight. Transient; retry shortly.NotStarted: the protocol is not running, so nothing would pump relay traffic or sweep the deadline for this lookup. Transient; retry afterstart().InvalidArgument: not a claimable username (empty, over 64 bytes once normalized, carrying a control or format character, or address-shaped).
username_resolved before calling this. The answer is a
single event with no replay, so a listener attached afterwards can miss it.
The answer carries every verified claim. There is deliberately no
“best” claim and no ordering: anyone may publish any name, so what comes
back is a set of assertions for a human to arbitrate, not a lookup result.
Do not auto-select. Taking the first entry turns a non-authoritative
directory into an authoritative-looking one: the user then believes the
name was verified when only a key ever was. Present the claims, have the
user confirm out of band, and store the address, never the name: a name
can be re-claimed by anyone tomorrow, an address is self-certifying.
returns: true if this call started the lookup, false if it joined one
throws: InvalidConfiguration if discovery is off, InvalidState if too
many lookups are in flight, NotStarted if the protocol is not running,
InvalidArgument if the name is not claimable
localAddress
This device’s own address (off1…), or null before startup completes.
Derived from the identity key held in this profile’s storage: the app
does not choose it, and it is stable across restarts of the same
profile. This is the string to show the user, put in an invite or QR
code, and what peers pass as the recipient to reach this device.
null until MLS is initialized (which start() does), because the key
that defines it lives in storage that is not open before then. The
identity_ready event carries the same value at the moment it is known.
deriveUserIdFromPublicKey
Derives a deterministic user ID from a public key. deprecated: UsederiveAddress. This returns the same off1…
address, but requires an initialized protocol instance and accepts any
input length rather than rejecting keys that are not 32 bytes.
returns: The derived address string
signData
Signs arbitrary data with the identity private key (Ed25519). Use this to prove ownership of your identity or to sign messages. returns: The signature as an array of bytes (64 bytes) throws: Error if MLS is not initializedverifySignature
Verifies a signature against a public key. Use this to verify that data was signed by the owner of a public key. returns: True if the signature is valid throws: Error if verification fails due to invalid inputmlsGenerateKeyPackage
Generates a new MLS key package. Key packages are used by others to establish encrypted sessions with you. returns: Generated key package throws: Error if generation failsmlsGetOrCreateKeyPackage
Gets an existing key package or creates a new one. returns: Key package throws: Error if operation failsmlsGetPendingKeyPackages
Gets pending key packages that haven’t been synced yet. returns: Array of pending key packagesmlsMarkKeyPackageSynced
Marks a key package as synced. throws: Error if operation failsmlsImportKeyPackage
Imports another user’s key package. Required before you can send encrypted messages to them. throws: Error if import failsmlsHasSession
Checks if an MLS session exists with another user. returns: True if session existshasPendingKeyPackage
Checks if a pending key package is available for a peer. Key packages are received automatically when peers are discovered (if auto_key_exchange is enabled). This method checks if we have received the peer’s key package and can establish a session. returns: True if key package is availablegetEstablishmentState
Returns the current secure-session establishment state for a peer. Useful for retry/UI flows when operations fail withSessionNotReady.
establishSecureSession
Establishes a secure MLS session with a peer (high-level API). This method handles the complete session establishment flow:- If session already exists, returns null
- If a pending key package is available, imports it, creates session, sends Welcome
- If no key package is available, throws an error
rekeySession
Rotate the 1:1 session with a peer, advancing post-compromise security. Post-compromise security arrives when a commit rotates a member’s leaf in the ratchet tree, and the SDK originates one on a re-key. Nothing drives a re-key on its own except an epoch desync, so a pair that never forks never rotates unless the application asks. That bites hardest against a leaf node, a lock, a sensor, which never commits at all, so every rotation in such a pair is this side’s to originate. The cadence is yours on purpose: a rotation costs a teardown, a key-package exchange and a re-establish, and what that is worth depends on the deployment rather than on anything the wire says. The peer sees exactly what a desync-driven re-key sends. Queued messages survive, because they are sealed at flush time against whatever session is current then. A rotation that fails changes nothing. The reset is advertised before the local session is torn down, so a rejection leaves the session intact, still usable, and the rate-limit window unspent. Rotate while the peer is reachable and treat a failure as “try again later” rather than as a session to rebuild. returns: true when the rotation was driven; false when the per-peer rate-limit window has not lapsed. False is not a failure: call again later. throws: Error if encryption is not initialized, the peer is blocked, there is no session to rotate (establish one first), or no transport carried the reset.mlsCreateSession
Creates an MLS session with another user. Returns a Welcome message that must be sent to the other user. Note: Prefer usingestablishSecureSession which handles the key package
flow automatically. This lower-level method requires the peer’s key package
to already be imported via mlsImportKeyPackage.
returns: Welcome message to send to the other user
throws: Error if session creation fails

