1 Node Identity
Claude edited this page 2026-02-19 23:29:30 +00:00

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