crypto

package
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 9, 2025 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package crypto provides cryptographic utilities for the discv5 protocol.

This package wraps and extends go-ethereum's crypto primitives with discv5-specific functionality:

  • ECDH key agreement for session establishment
  • AES-GCM encryption and decryption
  • HKDF key derivation for session keys

For basic key operations (generation, signing, verification), use github.com/ethereum/go-ethereum/crypto directly.

Index

Constants

View Source
const (
	// AESKeySize is the key size for AES-128 (16 bytes / 128 bits).
	// discv5 uses AES-128-GCM for message encryption.
	AESKeySize = 16

	// GCMNonceSize is the nonce size for GCM mode (12 bytes / 96 bits).
	// This is the recommended nonce size for AES-GCM.
	GCMNonceSize = 12

	// GCMTagSize is the authentication tag size for GCM mode (16 bytes / 128 bits).
	// This provides 128-bit authentication security.
	GCMTagSize = 16
)

Variables

View Source
var (
	// ErrInvalidKeySize is returned when an invalid key size is provided.
	ErrInvalidKeySize = fmt.Errorf("crypto: invalid key size, expected %d bytes", AESKeySize)

	// ErrInvalidNonceSize is returned when an invalid nonce size is provided.
	ErrInvalidNonceSize = fmt.Errorf("crypto: invalid nonce size, expected %d bytes", GCMNonceSize)

	// ErrDecryptionFailed is returned when GCM decryption/authentication fails.
	ErrDecryptionFailed = fmt.Errorf("crypto: decryption or authentication failed")
)

Functions

func AESGCMDecrypt

func AESGCMDecrypt(key, nonce, ciphertext, additionalData []byte) ([]byte, error)

AESGCMDecrypt decrypts ciphertext using AES-128-GCM.

Parameters:

  • key: 16-byte AES-128 key (must match encryption key)
  • nonce: 12-byte nonce (must match encryption nonce)
  • ciphertext: Encrypted data with authentication tag (from AESGCMEncrypt)
  • additionalData: Additional authenticated data (must match encryption AAD)

Returns plaintext if decryption and authentication succeed. Returns ErrDecryptionFailed if the ciphertext has been tampered with or if the key/nonce/AAD don't match.

Example:

plaintext, err := AESGCMDecrypt(key, nonce, ciphertext, nil)
if err != nil {
    // Decryption failed - either wrong key or tampered ciphertext
    return err
}

func AESGCMEncrypt

func AESGCMEncrypt(key, nonce, plaintext, additionalData []byte) ([]byte, error)

AESGCMEncrypt encrypts plaintext using AES-128-GCM.

Parameters:

  • key: 16-byte AES-128 key
  • nonce: 12-byte nonce (must be unique for each message with the same key)
  • plaintext: Data to encrypt
  • additionalData: Additional authenticated data (AAD) that is authenticated but not encrypted

Returns ciphertext with authentication tag appended (len = len(plaintext) + 16).

The nonce MUST be unique for each encryption with the same key. Reusing a nonce compromises security. In discv5, nonces are derived from message sequence numbers or generated randomly.

Example:

key := []byte{...} // 16 bytes
nonce, _ := GenerateRandomBytes(12)
ciphertext, err := AESGCMEncrypt(key, nonce, []byte("secret message"), nil)

func DecryptSession

func DecryptSession(decryptionKey, nonce, ciphertext, header []byte) ([]byte, error)

DecryptSession decrypts a message using session keys.

This is a convenience wrapper around AESGCMDecrypt that handles the session key format used in discv5.

Parameters:

  • decryptionKey: 16-byte session decryption key
  • nonce: 12-byte nonce (from message header)
  • ciphertext: Encrypted message
  • header: Protocol header (used as AAD)

Example:

decryptionKey := session.DecryptionKey()
plaintext, err := DecryptSession(decryptionKey, nonce, ciphertext, header)

func DeriveHandshakeSecrets

func DeriveHandshakeSecrets(nodeID, ephemeralSecret, challengeData []byte) ([]byte, error)

DeriveHandshakeSecrets derives secrets used during the handshake.

The discv5 handshake uses multiple derived secrets:

  • Key agreement key: For encrypting the handshake response
  • Challenge data: For binding the handshake to a specific node

This function derives these secrets using HKDF with appropriate context strings.

Parameters:

  • nodeID: Local node ID (32 bytes)
  • ephemeralSecret: ECDH shared secret from ephemeral keys
  • challengeData: Random challenge from WHOAREYOU

Example:

ephemeralSecret, _ := ECDH(ephemeralPriv, remotePub)
handshakeKey, err := DeriveHandshakeSecrets(localNodeID, ephemeralSecret, challenge)

func DeriveIDSignature

func DeriveIDSignature(nodePrivKey *ecdsa.PrivateKey, ephemeralPubKey *ecdsa.PublicKey) ([]byte, error)

DeriveIDSignature derives a signature key for node identity.

This is used in the handshake to prove ownership of a node ID without revealing the long-term private key.

func DeriveKey

func DeriveKey(secret []byte, context string, keyLen int) ([]byte, error)

DeriveKey is a general-purpose key derivation function.

This is a more flexible version of HKDFExtract that allows specifying custom context strings for different use cases.

Example:

// Derive different keys for different purposes from same secret
encKey := DeriveKey(secret, "encryption", 16)
macKey := DeriveKey(secret, "authentication", 32)

func DeriveKeyMaterial

func DeriveKeyMaterial(privKey *ecdsa.PrivateKey, pubKey *ecdsa.PublicKey, info []byte, keyLen int) ([]byte, error)

DeriveKeyMaterial derives key material from an ECDH shared secret.

This is a convenience function that combines ECDH and HKDF. It's the recommended way to derive session keys in discv5.

Parameters:

  • privKey: Local private key (static or ephemeral)
  • pubKey: Remote public key (static or ephemeral)
  • info: Context string for key derivation (e.g., "discovery v5 key agreement")
  • keyLen: Desired output key length in bytes

Example:

sessionKey, err := DeriveKeyMaterial(
    localPriv,
    remotePub,
    []byte("discovery v5 session key"),
    16, // 128-bit AES key
)

func DeriveSessionKeys

func DeriveSessionKeys(sharedSecret []byte, isInitiator bool, challengeData []byte) ([]byte, []byte, error)

DeriveSessionKeys derives both encryption and decryption keys from a shared secret.

In discv5, sessions use separate keys for each direction:

  • initiatorKey: Used by the initiator to encrypt, recipient to decrypt
  • recipientKey: Used by the recipient to encrypt, initiator to decrypt

This function derives both keys from the shared secret using HKDF with different context strings for domain separation.

Parameters:

  • sharedSecret: ECDH shared secret (32 bytes)
  • isInitiator: True if we are the session initiator, false if recipient
  • challengeData: Challenge data from WHOAREYOU packet (for binding)

Returns (ourEncryptionKey, ourDecryptionKey, error)

Example:

sharedSecret, _ := ECDH(localPriv, remotePub)
encKey, decKey, err := DeriveSessionKeys(sharedSecret, true, challengeData)
// Use encKey for encrypting outgoing messages
// Use decKey for decrypting incoming messages

func ECDH

func ECDH(privKey *ecdsa.PrivateKey, pubKey *ecdsa.PublicKey) ([]byte, error)

ECDH performs Elliptic Curve Diffie-Hellman key agreement.

Given a private key and a peer's public key, it computes a shared secret that both parties can derive independently. The shared secret is the X coordinate of the point: privKey * pubKey.

The discv5 protocol uses ECDH for session key establishment during the handshake process. The shared secret is then used as input to HKDF for deriving encryption keys.

Returns a 32-byte shared secret.

Example:

// Alice's side
alicePriv, _ := crypto.GenerateKey()
bobPub := // ... received from Bob ...
sharedSecret1, err := ECDH(alicePriv, bobPub)

// Bob's side
bobPriv, _ := crypto.GenerateKey()
alicePub := // ... received from Alice ...
sharedSecret2, err := ECDH(bobPriv, alicePub)

// sharedSecret1 == sharedSecret2

func EncryptSession

func EncryptSession(encryptionKey, nonce, message, header []byte) ([]byte, error)

EncryptSession encrypts a message using session keys.

This is a convenience wrapper around AESGCMEncrypt that handles the session key format used in discv5.

Parameters:

  • encryptionKey: 16-byte session encryption key
  • nonce: 12-byte nonce
  • message: Message to encrypt
  • header: Protocol header (used as AAD)

Example:

encryptionKey := session.EncryptionKey()
nonce := session.NextNonce()
ciphertext, err := EncryptSession(encryptionKey, nonce, message, header)

func ExtractNonceCounter

func ExtractNonceCounter(nonce []byte) (uint64, error)

ExtractNonceCounter extracts the counter from a nonce created by NonceFromUint64.

This is useful for replay detection and message ordering.

func GenerateNonce

func GenerateNonce() ([]byte, error)

GenerateNonce generates a random 12-byte nonce for AES-GCM.

Each nonce must be unique for a given key. In discv5, nonces are typically derived from message sequence numbers rather than generated randomly, to ensure uniqueness and allow replay detection.

Example:

nonce, err := GenerateNonce()
if err != nil {
    return err
}
ciphertext, _ := AESGCMEncrypt(key, nonce, plaintext, nil)

func GenerateRandomBytes

func GenerateRandomBytes(n int) ([]byte, error)

GenerateRandomBytes generates n random bytes using a cryptographically secure RNG.

This uses crypto/rand.Read which is the standard way to generate cryptographically secure random bytes in Go.

Example:

nonce, err := GenerateRandomBytes(16)
if err != nil {
    return err
}

func HKDFExtract

func HKDFExtract(salt, ikm, info []byte, keyLen int) ([]byte, error)

HKDFExtract performs HKDF key derivation (extract-and-expand).

HKDF (HMAC-based Key Derivation Function) is used in discv5 to derive session keys from the ECDH shared secret. It provides cryptographic separation between different uses of the same shared secret.

Parameters:

  • salt: Optional salt value (can be nil). Used for key stretching.
  • ikm: Input key material (e.g., ECDH shared secret)
  • info: Context-specific information (e.g., "discovery v5 session keys")
  • keyLen: Desired output length in bytes

The info parameter is crucial for domain separation - it ensures that keys derived for different purposes are cryptographically independent.

Returns keyLen bytes of key material.

Example:

sharedSecret, _ := ECDH(localPriv, remotePub)
sessionKey, err := HKDFExtract(
    nil, // No salt
    sharedSecret,
    []byte("discovery v5 session"),
    16, // 128-bit key
)

func NodeID

func NodeID(pubKey *ecdsa.PublicKey) []byte

NodeID returns the node ID derived from a public key.

The node ID is computed as:

nodeID = keccak256(uncompressed_public_key[1:])

This is the 32-byte identifier used in the Kademlia DHT and routing table.

Example:

privKey, _ := crypto.GenerateKey()
nodeID := NodeID(&privKey.PublicKey)
fmt.Printf("Node ID: %x\n", nodeID)

func NonceFromUint64

func NonceFromUint64(counter uint64) []byte

NonceFromUint64 converts a uint64 counter to a 12-byte nonce.

This is used in discv5 to derive nonces from message sequence numbers. The counter is encoded as a big-endian uint64 in the last 8 bytes, with the first 4 bytes set to zero.

Format: [0, 0, 0, 0, counter as 8 bytes big-endian]

Example:

nonce := NonceFromUint64(session.GetAndIncrementCounter())
ciphertext, _ := AESGCMEncrypt(key, nonce, plaintext, nil)

func SharedSecretToKey

func SharedSecretToKey(secret []byte) []byte

SharedSecretToKey derives a fixed-length key from a shared secret.

This is a simple wrapper around Keccak256 for backward compatibility. For new code, prefer using HKDF via DeriveKeyMaterial.

func ValidatePublicKey

func ValidatePublicKey(pubKey *ecdsa.PublicKey) error

ValidatePublicKey validates that a public key is on the secp256k1 curve.

This should be called when receiving public keys from untrusted sources to prevent invalid point attacks.

Returns nil if the key is valid, error otherwise.

func VerifyIDSignature

func VerifyIDSignature(sig []byte, nodeID []byte, ephemeralPubKey *ecdsa.PublicKey) bool

VerifyIDSignature verifies a node identity signature.

This is used to verify that a peer actually owns the claimed node ID.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL