Skip to content

Identities & Keys

It is easy to conflate “who a node is on the network” with “who authored an operation” — they are different keys with different lifetimes. Calimero uses three distinct kinds of key, and keeping them separate makes the rest of the protocol clear.

Transport identity Ed25519 → libp2p PeerId · per node · names the node on the wire node Member identity Ed25519 → namespace keypair · per namespace · authors & signs operations namespace Scope key symmetric · per scope · encrypts operations · delivered ECDH-wrapped scope

Every node has an Ed25519 keypair used only by the network layer. Its public key is the node’s peer id: the address other nodes dial, the identity the encrypted transport authenticates. It says nothing about authority — it answers “which box am I talking to,” not “who wrote this.” A node can serve many namespaces over one transport identity.

Authority lives in the member identity: an Ed25519 keypair held per namespace, generated on first use and persisted. It is the author on every operation and the key that signs an operation’s id (see Operations & the DAG). Membership, roles, and capabilities (defined in Governance) are all expressed in terms of member public keys.

Because identity is per namespace, the same node participating in two namespaces presents two unrelated member keys — identity isolation across application instances, independent of the single transport identity underneath.

A node is not limited to one member key per context. It can hold several member keypairs for the same context — each a ContextIdentity whose private key the node manages (crates/context/primitives/src/client/crypto.rs) — so one process can act as multiple distinct authors.

Because of that, every execute call names an executor: the executor: PublicKey on ExecuteRequest (crates/context/primitives/src/messages.rs) selects which of those member keys authors and signs the resulting operation. A context also carries a default member key — stored as the default identity alias — so calls that don’t name an executor fall back to it. Picking the executor is therefore picking who the write is attributed to, from among the keys this node controls.

A scope’s operations are encrypted at rest and on the wire under a symmetric scope key (every scope owns a key). Holding the key is what lets a member read a scope; lacking it, a node sees only opaque ciphertext.

Scope keys are never sent in the clear. They are delivered wrapped to a specific member’s public key using an ECDH exchange between that member key and the sender’s — the KeyDelivery step in Governance’s join flow. Removing a member rotates the scope key and re-wraps the fresh one to everyone who remains, which is how forward secrecy is enforced.

Put together, a single received operation touches all three: it arrives over a connection authenticated by transport identities, its ciphertext is decrypted with the scope key, and its authorship is verified against the author’s member key before anything trusts it. The Receive & Apply Path and Sync show that sequence on the wire.

The three keys above are all node-side — they live on a node and act inside a namespace. There is one more key role that belongs to a different actor entirely: the bundle signing key, an Ed25519 key held by whoever publishes an application.

When an application bundle is built, its manifest is signed with this key, and the public half is rendered as a did:key signerId — the bundle’s update authority (crates/node/primitives/src/bundle/signature.rs). That signerId is what anchors an application’s id across releases: a new version signed by the same key keeps the same application identity, and only the holder of that key can publish an update under it.

It is not a node or member key. It identifies an app publisher, not a box on the network or an author inside a context, and it never participates in the transport / member / scope sequence that processes an operation. Keep it separate when reasoning about authority: the first three say “who may write into a context”; the bundle key says “who may ship the code that context runs.”


Next: Encryption — which key encrypts what, and what an observer can still see.