Node Identity
Identity creation, key derivation, roles, and challenge-response authentication.
NodeIdentity
Every node in the mesh has a unique identity derived from an X25519 keypair. The node ID is computed from the public key, providing a cryptographically-bound identifier.
type NodeIdentity struct {
ID string // Derived from public key
Name string // Human-readable node name
PublicKey string // X25519 public key (hex-encoded)
CreatedAt time.Time // When the identity was created
Role NodeRole // controller, worker, or dual
}
Creating an Identity
NewNodeIdentity generates a fresh X25519 keypair and derives the node ID from the public key.
import "forge.lthn.ai/core/go-p2p/node"
// Create a controller node
controller := node.NewNodeIdentity("eu-controller-01", node.RoleController)
// Create a worker node
worker := node.NewNodeIdentity("gpu-worker-03", node.RoleWorker)
// Create a dual-role node (both controller and worker)
dual := node.NewNodeIdentity("homelab-node", node.RoleDual)
Node Roles
The NodeRole type determines a node's behaviour in the mesh.
type NodeRole string
const (
RoleController NodeRole = "controller" // Orchestrates work distribution
RoleWorker NodeRole = "worker" // Executes compute tasks
RoleDual NodeRole = "dual" // Both controller and worker
)
| Role | Description |
|---|---|
controller |
Manages the mesh, distributes tasks, collects results |
worker |
Receives and executes mining/compute workloads |
dual |
Participates as both controller and worker |
Authentication
go-p2p uses HMAC-SHA256 challenge-response for peer authentication. After X25519 ECDH key exchange establishes a shared secret, nodes prove their identity through the following flow:
Challenge Generation
// GenerateChallenge creates a 32-byte cryptographically random challenge
challenge := node.GenerateChallenge()
Signing a Challenge
// SignChallenge computes HMAC-SHA256(challenge, sharedSecret)
response := node.SignChallenge(challenge, sharedSecret)
Verifying a Response
// VerifyChallenge returns true if the response matches the expected HMAC
ok := node.VerifyChallenge(challenge, response, sharedSecret)
if !ok {
// Authentication failed — reject the peer
}
Authentication Flow
Node A Node B
| |
|--- handshake (public key) --->|
|<-- handshake_ack (pub key) ---|
| |
| [X25519 ECDH → shared secret]
| |
|<-- challenge (32 bytes) ------|
|--- SignChallenge(c, secret) ->|
| |
| [VerifyChallenge] |
| |
|<-- authenticated --------------|
The shared secret is never transmitted. Both sides derive it independently from the ECDH exchange, then use HMAC-SHA256 to prove possession without revealing the secret.
See Also
- Home — Package overview
- Protocol-Messages — Message types used in the handshake
- Peer-Discovery — How authenticated peers are managed