Skip to content

Mesh admin: operator channel and encrypted commands ​

Interactive maintenance (USB/BLE to a physically adjacent device) is Remote shell (0-hop). That path does not use mesh ttl.

This section defines operator actions over the mesh — e.g. delete or pin bulletin entries, change node parameters, MAC-layer blocklist, trigger config writes — in a way that is cryptographically separated from normal participants who only hold the network master key and everyday channel keys.

Threat model ​

  • Holders of the community PSK (NMK-derived channel keys) MUST not be able to forge admin actions by default.
  • Admin capabilities require possession of a separate admin channel encryption key (32-byte AES-256 secret).
  • Default community frames (channelHash == 0x0000) MUST NOT satisfy admin decryption: admin traffic MUST use the admin channelHash + adminChannelPsk only, so category 0x9 cannot be acted on from the community key alone.

Admin channel (normative) ​

ItemSpecification
channelHashAdmin frames use the channelHash computed from the fixed UTF-8 name lowmesh/admin + adminChannelPsk — see Channels & security.
Key materialadminChannelPsk — 32 bytes, AES-256, stored explicitly in NVM (see Embedded storage).
EncryptionAES-256-CCM on the application tail (same envelope as other mesh traffic): MIC_MODE in verFlags selects 4-byte (CCM-4, default) or 8-byte (CCM-8, optional) MIC. See Channels & security.
Payload typesAdmin payloadType values use category 0x9 — see Payloads. Nodes MUST reject (drop) category 0x9 unless decrypted with adminChannelPsk (matching admin channelHash). Community 0x0000 decrypt MUST NOT unlock admin bodies.

Routing: Admin frames use normal mesh headers (srcId, optional dstId, ttlRaw) and MAY be unicast to a specific infrastructure node or flood with tight TTL per policy.

ADMIN_CMD (0x91) ​

Opaque to non-admin decryptors. After MIC verification and decryption, the inner plaintext is:

FieldSizeDescription
adminCounter8LE uint64 monotonic counter per admin source / signing key
adminFlags1Bit 0 = signaturePresent; bits 1–7 reserved, MUST be 0 on TX
targetNodeId324LE nodeId32 target, or 0xFFFFFFFF for any admin host in scope
commandLen2LE length of commandChain
commandChaincommandLenAdmin operation chain (see below)
signature0 or 64Ed25519 signature when adminFlags & 0x01

There is one current ADMIN_CMD format (LowMesh is not publicly released — do not use ADMIN_CMD_V2 or adminBodyVersion naming).

Processing order (normative):

  1. MIC verification (CCM-4 or CCM-8 per MIC_MODE) — fail → drop, apply nothing.
  2. Product policy — e.g. CCM-8 required for destructive ops → fail → DROP_ADMIN_POLICY_REQUIRES_CCM8.
  3. Replay check on adminCounter — fail → DROP_ADMIN_REPLAY.
  4. Signature verify (if present or required) — fail → DROP_ADMIN_SIGNATURE_BAD / DROP_ADMIN_SIGNATURE_REQUIRED.
  5. Parse and apply commandChain — any failure → apply nothing.

commandChain (normative) ​

commandChain is one or more concatenated operation records:

[OpCode (1)] [KeyId (2 LE)] [ValueLen (2 LE)] [Value (ValueLen bytes)]

Rules:

  • Operations are processed in order.
  • If any record overruns commandLen, abort the entire command and apply nothing.
  • Unknown OpCode rejects the entire ADMIN_CMD and applies nothing.
  • For destructive commands, product policy MAY require CCM-8, a valid inner admin signature, or both.

Destructive / high-risk examples: key changes, admin key changes, firmware update state changes, deleting bulletin/store-forward data, changing radio/regional policy, changing relay permissions, factory reset, changing device role/profile.

Admin signature ​

When adminFlags & 0x01 (signaturePresent), a 64-byte Ed25519 signature follows commandChain.

Signed message (domain separator + fields):

"LowMesh ADMIN_CMD" ||
srcId ||
targetNodeId32 ||
adminCounter ||
commandLen ||
commandChain
  • srcId is the outer mesh header source nodeId32 (4 bytes, LE).
  • Do not include the routing block (repeaters may mutate breadcrumbs).
  • Do not include hopControl / ttlRaw (relays may decrement hops).

Receiver MUST verify the signature before applying commandChain. Invalid or missing signature when required → reject entire command, apply nothing.

Admin replay protection ​

Each receiver stores the highest accepted adminCounter per admin source identity or admin signing key. If adminCounter <= lastAcceptedCounter, reject as replay (DROP_ADMIN_REPLAY).

Implementations MAY keep a bounded replay window when storage is limited, but destructive commands SHOULD require persistent replay protection.

CCM-8 policy (admin) ​

Normal mesh traffic SHOULD use CCM-4. Admin traffic SHOULD use CCM-8 when both ends support SEC_CAP_CCM8. Products MAY require CCM-8 for destructive admin operations. If CCM-8 is required and the device does not support it, the command MUST NOT be sent or MUST be rejected.

See Channels & security — CCM-8 policy.

Universal execution chain (normative) ​

The commandChain is an implicit, sequential array of one or more operations, concatenated without outer TLV framing. Each operation is exactly:

[OpCode (1)] [KeyId (2 LE)] [ValueLen (2 LE)] [Value (ValueLen bytes)]

Records are processed in order. Let offset index the start of the current record in commandChain, valueLen the ValueLen field (2 LE), and commandLen the total commandChain length. If (offset + 5 + valueLen) > commandLen, the record overruns the buffer: the parser MUST abort the entire ADMIN_CMD (MUST NOT apply this record or any later record).

Valid OpCode values (only):

OpCodeName
0x10KV_SET (write / action intercept)
0x12KV_GET (read; response via ADMIN_RSP)

Any other OpCode is invalid; the receiver MUST reject the ADMIN_CMD and MUST NOT apply any operation from this message.

KV_GET request: ValueLen on the wire MUST be 0 (there is no request Value payload). Semantics are selected solely by KeyId.

Parsing / execution (normative, firmware): Let off = 0. While off < commandLen:

  1. If off + 5 > commandLen, abort entire command — apply nothing.
  2. Read OpCode = commandChain[off], KeyId (2 LE at off+1), ValueLen (2 LE at off+3).
  3. If (off + 5 + ValueLen) > commandLen, abort entire command (overrun).
  4. Let Value = commandChain + off + 5 (length ValueLen).
  5. Dispatch KV_SET / KV_GET for KeyId; on any failure, abort entire command (MUST NOT apply later operations).
  6. off += 5 + ValueLen.

An empty commandChain (commandLen = 0) is valid (no-op) after replay/signature/policy checks pass.

Atomic two-phase commandChain commit (normative) ​

commandChain execution is atomic: receivers MUST use a two-phase apply model:

  1. Validate phase — Parse the full commandChain, verify every record fits within commandLen, resolve KeyId / Value bounds, and run policy pre-checks (CCM-8, signature, unicast dstId, dangerous-op rules below). No NVM or RAM mutation in this phase.
  2. Commit phase — Only if validation succeeds, apply all operations in order. If any operation fails during commit, the receiver MUST roll back to the pre-command state where feasible and MUST NOT leave a partially applied chain.

If validation fails, nothing from that ADMIN_CMD is applied — same as a mid-chain parse failure.

Dangerous operations — delivery and policy (normative) ​

Destructive or high-risk KV_SET operations (key changes, admin key changes, firmware update state, bulletin/store-forward deletes, radio/regional policy, relay permissions, factory reset, role/profile changes) MUST satisfy all of the following unless a documented product exemption exists:

RequirementRule
Unicast dstIdOuter mesh header HAS_DST_ID MUST be set; dstId MUST equal the intended target nodeId32 (not flood, not 0xFFFFFFFF wildcard)
Replay protectionadminCounter MUST pass the monotonic replay check
Signature policyProduct policy MAY require adminFlags & signaturePresent and a valid Ed25519 signature over the signed message domain
CCM-8 policyProduct policy MAY require MIC_MODE = 01 when SEC_CAP_CCM8 is advertised on the target

Flood-delivered admin frames MAY be accepted for non-destructive reads (KV_GET) per product policy, but MUST NOT satisfy dangerous-op delivery rules above.

Vendor or product-specific extensions SHOULD use dedicated KeyId blocks (e.g. high 0xExxx ranges) and MUST document wire Value layouts.

Virtual register table (KeyId LE) ​

Normative registry, listed by command name. KeyId values are grouped by high byte (0x01xx standard config, 0x02xx bulletin host intercepts, 0x03xx RAM MAC blocklist). KV_SET carries Value as below; KV_GET responses use ADMIN_RSP echo blocks (KeyId (2 LE) || valueLen (2 LE) || value) as in Getter responses under ADMIN_RSP below.

Command nameKeyId (LE)KV_SET ValueNotes
MAC_RELAY_COMMUNITY0x01011 byte: 0x00 = Restricted (Fortress: drop unknown channelHash without local key); 0x01 = Community (forward unknown ciphertext per Channels & security).Updates NVM / RAM policy; maps to nvm::DeviceRole::relayCommunityTraffic. KV_GET: current 1 byte.
MAC_PRIMARY_ROLE0x01021 byte: 0x00 = Gateway, 0x01 = Repeater, 0x02 = Client (legacy alias: Edge)Updates NVM role; nvm::DeviceRole::primaryRole. KV_GET: 1 byte.
BULLETIN_CAP_TOTAL0x01112 bytes LE (nMaxTotal)Updates NVM bulletin host capacity; BulletinHostPrefs (see Bulletin board). KV_GET: 2 bytes LE.
BULLETIN_DELETE0x02016 bytes: channelHash (2 LE) `
BULLETIN_PIN0x02027 bytes: channelHash (2 LE) `
BLOCKLIST_ADD0x0301srcId (4 LE)RAM blocklist add (see Embedded storage).
BLOCKLIST_REMOVE0x0302srcId (4 LE)RAM blocklist remove.

KV_GET (0x12) on KeyId = 0x0301 (BLOCKLIST_ADD): returns the current blocklist array in the echoed value: count (1 byte) then count × 4 bytes of srcId (LE), insertion order, implementation cap (e.g. 16 entries). The response echo’s KeyId field is still 0x0301.

Persistence (normative): On successful KV_SET for 0x0101, 0x0102, or 0x0111, firmware MUST persist the corresponding NVM fields (lowmesh::nvm::DeviceStoreV1, BulletinHostPrefs, or equivalent) immediately before reporting success to the operator path, so reboot and power loss do not revert policy. RAM-only updates are not sufficient for those keys.

C++: lowmesh::runtime::ADMIN_OP_KV_SET, ADMIN_OP_KV_GET, ADMIN_KV_* — see firmware/include/lowmesh/runtime/admin_cmd.hpp.

Example: instant relay lockdown (Restricted) ​

Single KV_SET as the entire commandChain (commandLen = 6, no signature):

Byte (hex)Meaning
10OpCode = KV_SET
01 01KeyId = 0x0101 (MAC_RELAY_COMMUNITY, LE)
01 00ValueLen = 1, LE
00Value = 0x00 → Restricted

Full commandChain (6 bytes): 10 01 01 01 00 00.

ADMIN_RSP (0x92) ​

Unicast response from target to operator originator (when applicable):

FieldSizeDescription
forMessageId2Correlates to admin request mesh messageId (16-bit)
status1Status code — see table below
bodyLen2LE
bodyvariableStructured payload per status / command

status values (normative):

ValueName
0x00ADMIN_STATUS_OK
0x01ADMIN_STATUS_BAD_FORMAT
0x02ADMIN_STATUS_REPLAY
0x03ADMIN_STATUS_SIGNATURE_REQUIRED
0x04ADMIN_STATUS_SIGNATURE_BAD
0x05ADMIN_STATUS_POLICY_REQUIRES_CCM8
0x06ADMIN_STATUS_UNSUPPORTED_MIC_MODE

ADMIN_RSP SHOULD use the same admin channelHash / domain and SHOULD use the same or stronger MIC_MODE as the request when possible.

Getter responses (KV_GET → ADMIN_RSP) ​

For each KV_GET operation successfully executed in order, the responder SHOULD append one block to ADMIN_RSP body with the same echo shape:

KeyId (2 LE) || valueLen (2 LE) || value

  • Example: KV_GET on BLOCKLIST_ADD (0x0301) yields value = count (1) || count × srcId (4 LE each) inside that echo block (see virtual register table).
  • Multiple **KV_GET**s in one ADMIN_CMD chain → concatenate echo blocks in execution order (firmware: State::adminChainRsp filled by dispatchSecureAdminCmdPlaintext on success).

Errors: non-zero status; optional UTF-8 / structured error in body. On chain parse/execute failure, the requester MUST treat the whole ADMIN_CMD as failed; ADMIN_RSP payload from a partial chain is not normative.

Bulletin board interaction ​

Public BULLETIN_POST on normal channels remains for authors. Moderation (delete, pin, capacity) over RF uses ADMIN_CMD on the admin channelHash with the admin key — see Bulletin board.

Relationship to USB/BLE provisioning ​

Provisioning still uses USB & BLE hex API for initial key install. The admin mesh key SHOULD be distributed only to operators (QR, secure BLE, or out-of-band), not embedded in general community QR codes.

Embedded storage ​

See adminChannelPsk — stored in lowmesh::nvm::Crypto::adminChannelPsk in Embedded storage — and Crypto keys.

LowMeshOS — always open-source mesh protocol documentation