A HiveMind node has one identity: one keypair and one Noise static key, used in both directions — serving its own clients and dialling its master. The identity is persisted in:
~/.config/hivemind/_identity.json
HIVEMIND-CRYPTO-1 §2: a node is one application, not the user or the host. Two applications of one user are two nodes and must not present the same identifier or static key. Pass the application's own name:
identity = NodeIdentity(app_name="voice-sat")
# ~/.config/hivemind/voice-sat/_identity.json, keys in the same folderapp_name is one path segment: letters, digits, ., _ and -, starting
with a letter or digit, at most 64 characters. Passing both identity_file
and app_name raises ValueError.
NodeIdentity() without a name uses the shared
~/.config/hivemind/_identity.json. §2 does not allow that location as a
default, so an application passes its own app_name.
Every deployment before app_name was provisioned with
hivemind-client set-identity, which writes the shared file. When an
application starts to name itself on such a box, it has no file of its own
yet. NodeIdentity(app_name=...) then reads the shared file, logs a
warning that names §2 and the command that gives the application its own
identity, and sets identity.uses_shared_fallback to True. It never
creates the shared file: without one, the application gets its own path.
hivemind-client --app voice-sat set-identity --key KEY --password PASS --host HUBwrites ~/.config/hivemind/voice-sat/_identity.json, and the warning
stops. set-identity and reset-pgp pass shared_fallback=False, so
they never touch the shared file. Everything else works on the file the
application reads: under the fallback a pin the application adds on its
first connection, or drops with forget-server, is the pin every other
application on the box checks. Give the application its own file before
you change pins.
Credentials (access_key, password), useragent and site_id are not
part of the identity. They say how to reach one particular master, and a
node that both serves clients and connects upstream has its own access key
and password as well as its master's. The client reads them from the
identity file only as a fallback, and never writes them back — see
One identity, many masters below.
| Field | Description |
|---|---|
name |
Human-readable node label (not required to be unique). Read as the client's useragent fallback |
access_key |
Access key issued by hivemind-core add-client. Read as a fallback when the client is not given one directly |
password |
Password used to derive the AES session key during handshake. Read as a fallback when the client is not given one directly |
default_master |
Default hub URL (e.g. ws://192.168.1.10) |
default_port |
Default hub port (e.g. 5678) |
site_id |
Location identifier injected into every OVOS message context. Read as a fallback when the client is not given one directly |
public_key |
RSA public key for this node |
secret_key |
Path to the RSA private key PEM file |
trusted_keys |
Dict of alias → public key for trusted peers (see below) |
A node's identity is its keypair. Credentials are not stored in the
identity in the sense of being owned by it — they are read from the
identity file as a fallback, and a client given its own key/password
(directly, or via set-identity) uses those instead.
connect() resolves credentials this way:
self._access_key = self._access_key or self.identity.access_key
self._password = self._password or self.identity.password
self._site_id = site_id or self._site_id or self.identity.site_idNothing here is ever written back to the identity file. That distinction
matters for a node that both serves its own downstream clients and connects
to a master above it: it has its own access key and password, and its
master's. Earlier, the client copied whatever credentials it was handed
onto self.identity and the first successful Noise handshake persisted
them (pinning a peer key writes the whole identity to disk) — so the node's
own access key, password, and name were silently overwritten by its
master's on first connect. Every downstream client of that node then failed
its own handshake with "invalid api key" against credentials the node no
longer had, and the node reported itself under the default useragent
instead of its own name.
Peers whose public key is in trusted_keys are allowed to inject BUS
messages via PROPAGATE and INTERCOM. Untrusted peers are silently dropped.
from hivemind_bus_client.identity import NodeIdentity
identity = NodeIdentity()
identity.add_trusted_key("living-room-hub", "<RSA_PUBLIC_KEY>")
identity.save()
# Check trust
identity.is_trusted_key("<RSA_PUBLIC_KEY>") # True
identity.get_trusted_alias("<RSA_PUBLIC_KEY>") # "living-room-hub"
# Remove
identity.remove_trusted_key("living-room-hub")
identity.save()Source: NodeIdentity.trusted_keys, identity.py
hivemind-client set-identity \
--key "42caf3d2405075fb9e7a4e1ff44e4c4f" \
--password "5ae486f7f1c26bd4645bd052e4af3ea3" \
--siteid "living-room"This writes the values to ~/.config/hivemind/_identity.json. You can also set default_master / default_port so you don't need to pass --host / --port on every invocation.
from hivemind_bus_client.identity import NodeIdentity
identity = NodeIdentity()
# Read persisted values
print(identity.access_key)
print(identity.password)
print(identity.default_master)
print(identity.site_id)
# Override values programmatically
identity.access_key = "my-key"
identity.password = "my-password"
identity.default_master = "ws://192.168.1.10"
identity.default_port = 5678
identity.save()NodeIdentity stores an RSA key pair used for the HiveMind handshake and INTERCOM (peer-to-peer encrypted) messages.
identity.create_keys() # generates and saves a new RSA key pair
print(identity.public_key) # PEM-encoded public key string
print(identity.private_key) # path to the private key .pem fileKeys are auto-created on first use if they do not exist.
Credentials can be passed directly to the client constructor instead of relying on the identity file:
from hivemind_bus_client.client import HiveMessageBusClient
client = HiveMessageBusClient(
host="ws://192.168.1.10",
port=5678,
key="42caf3d2405075fb9e7a4e1ff44e4c4f",
password="5ae486f7f1c26bd4645bd052e4af3ea3",
)
client.connect()Both key and password are required. If either is missing and not present in the identity file, a RuntimeError is raised.