March Docs

Handshake

Handshake: the net-kernel greeting + authentication decision.

When two nodes connect, each sends a Hello { its identity, a fresh nonce }. Each then replies with a proof = ClusterAuth.prove(secret, peer_nonce). A node accepts the peer iff the peer's proof matches the nonce it issued — so only holders of the cluster secret join, and a proof can't be replayed for a different nonce. This module is the pure protocol logic; the socket I/O (framing via NetFrame, send/recv) lives in the net-kernel on top.

Types

typeHelloCertHelloCert = { cert : List(Int), eph : List(Int) }#

role names the connection this hello opens: "control" (SWIM, registry sync, monitors, CREDIT, DELIVERY_FAILED) or "data" (ACTOR_MSG, RPC). One of each per peer since the control/data split (specs/progress/2026-09-14-distributed-plane-flow-control-and-control-channel.md, step 2). A hello without the field -- every node before the split -- is a control connection, so the two wire shapes interoperate.

addr is the sender's ADVERTISED listen address ("host:port"), so an acceptor learns where to dial the peer back before any gossip arrives (the cluster node service). "" when not sent: a hello without it (every node before the service) decodes as before.

cert is present only in CERTIFICATE mode (ClusterAuth.Certified): the sender's signed certificate (NodeCert.to_bytes) and a fresh ephemeral X25519 public key for the connection's MAC key agreement. It is sent as a seventh element, ["cert1", Bin(cert), Bin(eph)]; a shared-secret hello never has it and is byte-identical to a pre-11a node's.

typeHelloHello = { identity : NodeIdentity.Identity, nonce : String, role : String, addr : String,#

Functions

fnauthenticateauthenticate(secret : String, our_nonce : String, peer : Hello,#

Shared-secret mode's decision. Given the cluster [secret], the nonce [our_nonce] we issued to the peer, the peer's decoded hello [peer], and the peer's proof [peer_proof]: Ok with the identity the hello claims when the proof is ClusterAuth.prove(secret, our_nonce), else Err("Handshake: peer failed authentication"). A proof made for another nonce never verifies.

fncheck_modecheck_mode(mine : ClusterAuth.Auth, peer : Hello) : Result((), String)#

The two modes do not mix: a certificate-mode node refuses a shared-secret peer and a shared-secret node refuses a certificate-mode one, each naming what the other side must be given. (A pre-11a node cannot decode a certificate hello at all and fails with "malformed hello".) Ok(()) when the modes agree.

fndecode_hellodecode_hello(bytes : List(Int)) : Result(Hello, String)#

Decode the bytes of encode_hello, and the older shapes: a 6-element hello (no certificate), a 5-element one (no address: addr ""), and a

  1. Err

for bytes that are not MessagePack, or "Handshake: malformed hello".

fnencode_helloencode_hello(h : Hello) : List(Int)#

Serialize [h] to MessagePack bytes: [name, node_id, incarnation, nonce, role], then [addr] when it is not "" (so a hello without an address is byte-identical to the pre-service shape). A certificate hello always carries the address (possibly "") and then ["cert1", Bin(cert), Bin(eph)].

fnmake_hellomake_hello(identity : NodeIdentity.Identity, nonce : String) : Hello#

A shared-secret hello from [identity] with challenge [nonce], opening a control connection and advertising no address.

fnmake_hello_addrmake_hello_addr(identity : NodeIdentity.Identity, nonce : String, role : String, addr : String) : Hello#

As make_hello_role, advertising [addr] (\

fnmake_hello_certmake_hello_cert(identity : NodeIdentity.Identity, nonce : String, role : String, addr : String,#

A certificate-mode hello: as make_hello_addr, carrying this node's signed certificate [cert] (NodeCert.to_bytes) and the connection's ephemeral X25519 public key [eph].

fnmake_hello_rolemake_hello_role(identity : NodeIdentity.Identity, nonce : String, role : String) : Hello#

As make_hello, opening a connection of [role] (role_control() or role_data()).

fnrole_controlrole_control() : String do "control" end#

The role of a control connection (SWIM, registry sync, monitors, CREDIT, DELIVERY_FAILED): \

fnrole_datarole_data() : String do "data" end#

The role of a data connection (ACTOR_MSG, RPC): \

fnverify_peer_certverify_peer_cert(creds : NodeCert.Credentials, peer : Hello, now : Int) : Result((NodeCert.Cert, Bytes), String)#

Check the certificate a certificate-mode peer sent at [now] (unix seconds): signed by the operator key, not expired, not revoked, and naming the node the hello claims to be -- its name is the certificate URI's last segment, and its node id is that name's (NodeIdentity.name_id). Returns the certificate and the node's public key, or a reason.