Documentation
¶
Overview ¶
Package session implements session management and encryption for discv5.
The session module handles:
- WHOAREYOU challenge/response handshakes
- Session key derivation using ECDH + HKDF
- AES-GCM encryption and decryption
- Session caching and lifecycle management
- Nonce tracking to prevent replay attacks
Index ¶
- Constants
- func DecryptMessage(key, nonce, authData, ciphertext []byte) ([]byte, error)
- func EncryptMessage(key, nonce, authData, plaintext []byte) ([]byte, error)
- func GenerateEphemeralKey() (*ecdsa.PrivateKey, error)
- type Cache
- func (c *Cache) CleanupExpired() int
- func (c *Cache) Close() error
- func (c *Cache) Count() int
- func (c *Cache) Delete(nodeID node.ID)
- func (c *Cache) Get(nodeID node.ID) *Session
- func (c *Cache) GetByAddr(addr *net.UDPAddr) *Session
- func (c *Cache) GetNodeByID(nodeID node.ID) *node.Node
- func (c *Cache) GetStats() Stats
- func (c *Cache) Lifetime() time.Duration
- func (c *Cache) Put(session *Session)
- type Session
- func (s *Session) Age() time.Duration
- func (s *Session) DecryptionKey() []byte
- func (s *Session) EncryptionKey() []byte
- func (s *Session) GetNode() *node.Node
- func (s *Session) IdleTime() time.Duration
- func (s *Session) IsExpired() bool
- func (s *Session) SetNode(n *node.Node)
- func (s *Session) String() string
- func (s *Session) TimeUntilExpiry() time.Duration
- func (s *Session) Touch()
- func (s *Session) UpdateAddr(addr *net.UDPAddr)
- type SessionKeys
- type Stats
Constants ¶
const DefaultMaxSessions = 1000
DefaultMaxSessions is the default maximum number of cached sessions.
const DefaultSessionLifetime = 30 * time.Minute
DefaultSessionLifetime is the default lifetime for sessions (30 minutes).
Variables ¶
This section is empty.
Functions ¶
func DecryptMessage ¶
DecryptMessage decrypts a message using AES-128-GCM.
The decryption uses:
- Key: 16-byte session key (initiator or recipient key)
- Nonce: 12-byte nonce from packet header
- Additional Data: Complete packet header (IV + masked static header + masked authdata)
The ciphertext must include the 16-byte authentication tag at the end.
According to discv5 spec, authData for GCM is the entire header data before the message.
Example:
plaintext, err := DecryptMessage(key, nonce, headerData, ciphertext)
func EncryptMessage ¶
EncryptMessage encrypts a message using AES-128-GCM.
The encryption uses:
- Key: 16-byte session key (initiator or recipient key)
- Nonce: 12-byte nonce from packet header
- Additional Data: Complete packet header (IV + masked static header + masked authdata)
Returns the ciphertext with appended authentication tag (16 bytes).
According to discv5 spec, authData for GCM is the entire header data before the message. For ordinary packets, this is typically 71 bytes (16 IV + 23 header + 32 authdata).
Example:
ciphertext, err := EncryptMessage(key, nonce, headerData, plaintext)
func GenerateEphemeralKey ¶
func GenerateEphemeralKey() (*ecdsa.PrivateKey, error)
GenerateEphemeralKey generates a new ephemeral ECDSA key pair.
Ephemeral keys are used once per session and discarded after the session keys are derived.
Example:
ephemeralKey, err := GenerateEphemeralKey()
if err != nil {
return err
}
Types ¶
type Cache ¶
type Cache struct {
// contains filtered or unexported fields
}
Cache manages active sessions with peers.
The cache provides:
- Fast session lookup by node ID and address
- Automatic session expiration and cleanup
- LRU eviction when the cache is full
- Thread-safe concurrent access
func NewCache ¶
NewCache creates a new session cache.
Parameters:
- maxSessions: Maximum number of sessions to cache (0 = unlimited)
- sessionLifetime: How long sessions remain valid (0 = default 30min)
- logger: Optional logger for debug messages
Example:
cache := NewCache(1000, 30*time.Minute, logger) defer cache.Close()
func (*Cache) CleanupExpired ¶
CleanupExpired removes all expired sessions from the cache.
This should be called periodically by a background goroutine.
Returns the number of sessions removed.
Example:
count := cache.CleanupExpired()
log.Printf("Removed %d expired sessions", count)
func (*Cache) Get ¶
Get retrieves a session by node ID.
Returns nil if no session exists or if the session has expired.
Example:
session := cache.Get(remoteNodeID)
if session != nil {
// Use session for encryption
}
func (*Cache) GetByAddr ¶
GetByAddr retrieves a session by network address.
This is slower than GetByID since it requires scanning all sessions. Use GetByID when possible.
func (*Cache) GetNodeByID ¶
GetNodeByID retrieves a node by ID from the session cache.
Returns nil if no session exists or if the session has no node reference.
This allows the session cache to act as a local node lookup table.
type Session ¶
type Session struct {
// RemoteID is the node ID of the remote peer
RemoteID node.ID
// RemoteAddr is the network address of the remote peer
RemoteAddr *net.UDPAddr
// Node is the full node information (ENR, etc.)
// This allows protocol operations to access node data without a separate table lookup
Node *node.Node
// Keys contains the encryption keys for this session
Keys *SessionKeys
// IsInitiator indicates if we initiated this session
IsInitiator bool
// CreatedAt is when the session was established
CreatedAt time.Time
// ExpiresAt is when the session expires
ExpiresAt time.Time
// LastUsed is the last time this session was used
LastUsed time.Time
// contains filtered or unexported fields
}
Session represents an active encrypted session with a peer.
Each session has:
- Unique session keys derived from ECDH
- Creation and expiration timestamps
- Role (initiator or recipient)
- Nonce tracking for replay protection
- Node reference for protocol operations
func NewSession ¶
func NewSession( remoteID node.ID, remoteAddr *net.UDPAddr, keys *SessionKeys, isInitiator bool, lifetime time.Duration, ) *Session
NewSession creates a new session.
Parameters:
- remoteID: Node ID of the remote peer
- remoteAddr: Network address of the remote peer
- keys: Derived session keys
- isInitiator: True if we initiated the session
- lifetime: How long the session is valid (default 30 minutes)
Example:
session := NewSession(remoteID, remoteAddr, keys, true, 30*time.Minute)
func (*Session) DecryptionKey ¶
DecryptionKey returns the key to use for decrypting incoming messages.
The key is the opposite of the encryption key.
func (*Session) EncryptionKey ¶
EncryptionKey returns the key to use for encrypting outgoing messages.
The key depends on whether we are the session initiator or recipient.
func (*Session) GetNode ¶
GetNode returns the node reference for this session.
Returns nil if no node has been set.
func (*Session) IsExpired ¶
IsExpired checks if the session has expired.
Sessions expire after their lifetime (default 12 hours) or can be manually expired by the session manager.
func (*Session) SetNode ¶
SetNode updates the node reference for this session.
This is typically called after a handshake when we receive the remote node's ENR.
func (*Session) TimeUntilExpiry ¶
TimeUntilExpiry returns how long until the session expires.
Returns 0 if already expired.
func (*Session) Touch ¶
func (s *Session) Touch()
Touch updates the last used timestamp.
This is called whenever the session is used for encryption or decryption.
func (*Session) UpdateAddr ¶
UpdateAddr updates the remote address for this session.
This is called when we detect that a node has moved to a different IP address.
type SessionKeys ¶
type SessionKeys struct {
// InitiatorKey is used by the session initiator for encryption
InitiatorKey []byte
// RecipientKey is used by the session recipient for encryption
RecipientKey []byte
// AuthRespKey is used for authenticating handshake responses
AuthRespKey []byte
}
SessionKeys contains the derived encryption keys for a session.
Keys are derived from the ECDH shared secret using HKDF-SHA256 with protocol-specific context strings.
func DeriveSessionKeys ¶
func DeriveSessionKeys( secret []byte, initiatorNodeID node.ID, recipientNodeID node.ID, challengeData []byte, ) (*SessionKeys, error)
DeriveSessionKeys derives session keys from an ECDH shared secret.
The key derivation follows the discv5 specification:
- Compute ECDH shared secret from ephemeral keys
- Use HKDF-SHA256 to derive three keys: - initiator-key (16 bytes) - recipient-key (16 bytes) - auth-resp-key (16 bytes)
Parameters:
- secret: ECDH shared secret
- initiatorNodeID: Node ID of the session initiator
- recipientNodeID: Node ID of the session recipient
- challengeData: Challenge data from WHOAREYOU packet (idnonce)
Example:
secret, _ := crypto.ComputeECDH(localPrivKey, remotePubKey) keys, err := DeriveSessionKeys(secret, localNodeID, remoteNodeID, challenge)