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 = 0x00Restricted

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_GETADMIN_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