slackhq/nebula · error
no cipher state available to encrypt
Error message
no cipher state available to encrypt
What it means
Identical guard to the AES-GCM wrapper but for ChaCha20-Poly1305: if the CipherStateChaChaPoly receiver is nil there is no AEAD to encrypt with, so EncryptDanger returns this error rather than panicking.
Source
Thrown at noiseutil/chachapoly.go:25
"github.com/flynn/noise"
)
// CipherStateChaChaPoly is the data-plane wrapper for the ChaCha20-Poly1305 AEAD cipher.
// ChaCha20-Poly1305 uses little-endian nonce encoding per the Noise spec.
type CipherStateChaChaPoly struct {
c cipher.AEAD
}
// NewCipherStateChaChaPoly extracts the underlying AEAD from the post-handshake noise.CipherState.
// The caller is responsible for ensuring the noise cipher is actually ChaCha20-Poly1305.
func NewCipherStateChaChaPoly(s *noise.CipherState) *CipherStateChaChaPoly {
return &CipherStateChaChaPoly{c: s.Cipher().(cipher.AEAD)}
}
func (s *CipherStateChaChaPoly) EncryptDanger(out, ad, plaintext []byte, n uint64, nb []byte) ([]byte, error) {
if s == nil {
return nil, errors.New("no cipher state available to encrypt")
}
if n >= RejectAfterMessages {
return nil, ErrMessageCounterExhausted
}
nb[0] = 0
nb[1] = 0
nb[2] = 0
nb[3] = 0
binary.LittleEndian.PutUint64(nb[4:], n)
return s.c.Seal(out, nb, plaintext, ad), nil
}
func (s *CipherStateChaChaPoly) DecryptDanger(out, ad, ciphertext []byte, n uint64, nb []byte) ([]byte, error) {
if s == nil {
return []byte{}, nil
}
nb[0] = 0
nb[1] = 0View on GitHub (pinned to dd8f660c0a)
Solutions
- Verify the handshake produced a valid *noise.CipherState before wrapping with NewCipherStateChaChaPoly.
- Guard the send path with a nil check on the ChaChaPoly cipher state.
- If using CipherChoice_ChaChaPoly, confirm the peer negotiated the same cipher choice so a state is actually established.
Example fix
// before
if s == nil { return nil, errors.New("no cipher state available to encrypt") }
// after: caller-side
if chacha == nil { rehandshake(); } Defensive patterns
Strategy: type-guard
Validate before calling
if cs == nil {
return errors.New("chachapoly cipher state not initialized")
} Type guard
func hasCipherState(cs *noiseutil.CipherStateChaChaPoly) bool { return cs != nil } Try / catch
out, err := cs.EncryptDanger(out, ad, plaintext, n, nb)
if err != nil {
// nil state or counter exhaustion: force re-handshake
} Prevention
- Gate the send path on a completed handshake flag.
- Check for nil before caching the cipher state in session maps.
- Confirm cipher choice negotiation matches the peer.
When it happens
Trigger: Calling EncryptDanger on a nil *CipherStateChaChaPoly, typically produced by NewCipherStateChaChaPoly(nil) or an unset post-handshake state; covered by TestCipherStateNilSafety.
Common situations: Failed or skipped noise handshake leaving the send-side cipher unset, storing cipher states in a map and hitting a missing key, or tests constructing the wrapper directly without an underlying state.
Related errors
- no cipher state available to encrypt
- no cipher state available to encrypt
- ErrIncompleteHandshake
- ErrMachineFailed
- ErrMissingContent
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/bd7f393eb7c0f770.
Report an issue: GitHub.