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
- Variables
- func AESGCMDecrypt(key, nonce, ciphertext, additionalData []byte) ([]byte, error)
- func AESGCMEncrypt(key, nonce, plaintext, additionalData []byte) ([]byte, error)
- func DecryptSession(decryptionKey, nonce, ciphertext, header []byte) ([]byte, error)
- func DeriveHandshakeSecrets(nodeID, ephemeralSecret, challengeData []byte) ([]byte, error)
- func DeriveIDSignature(nodePrivKey *ecdsa.PrivateKey, ephemeralPubKey *ecdsa.PublicKey) ([]byte, error)
- func DeriveKey(secret []byte, context string, keyLen int) ([]byte, error)
- func DeriveKeyMaterial(privKey *ecdsa.PrivateKey, pubKey *ecdsa.PublicKey, info []byte, keyLen int) ([]byte, error)
- func DeriveSessionKeys(sharedSecret []byte, isInitiator bool, challengeData []byte) ([]byte, []byte, error)
- func ECDH(privKey *ecdsa.PrivateKey, pubKey *ecdsa.PublicKey) ([]byte, error)
- func EncryptSession(encryptionKey, nonce, message, header []byte) ([]byte, error)
- func ExtractNonceCounter(nonce []byte) (uint64, error)
- func GenerateNonce() ([]byte, error)
- func GenerateRandomBytes(n int) ([]byte, error)
- func HKDFExtract(salt, ikm, info []byte, keyLen int) ([]byte, error)
- func NodeID(pubKey *ecdsa.PublicKey) []byte
- func NonceFromUint64(counter uint64) []byte
- func SharedSecretToKey(secret []byte) []byte
- func ValidatePublicKey(pubKey *ecdsa.PublicKey) error
- func VerifyIDSignature(sig []byte, nodeID []byte, ephemeralPubKey *ecdsa.PublicKey) bool
Constants ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
ExtractNonceCounter extracts the counter from a nonce created by NonceFromUint64.
This is useful for replay detection and message ordering.
func GenerateNonce ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
Types ¶
This section is empty.