slackhq/nebula · critical
ErrAsymmetricCipherKeys
ErrAsymmetricCipherKeys
Error message
noise produced only one cipher key
What it means
ErrAsymmetricCipherKeys is returned by ProcessPacket when a Noise handshake message that should finalize key derivation yields only one of the two transport cipher keys (encryption or decryption key is nil). A correct handshake always produces a key pair for both directions, so this indicates a pattern/engine inconsistency. The machine is marked failed.
Source
Thrown at handshake/errors.go:19
package handshake
import "errors"
var (
ErrInitiateOnResponder = errors.New("initiate called on responder")
ErrInitiateAlreadyCalled = errors.New("initiate already called")
ErrInitiateNotCalled = errors.New("initiate must be called before ProcessPacket for initiators")
ErrPacketTooShort = errors.New("packet too short")
ErrPublicKeyMismatch = errors.New("public key mismatch between certificate and handshake")
ErrIncompleteHandshake = errors.New("handshake completed without receiving required content")
ErrMachineFailed = errors.New("handshake machine has failed")
ErrUnknownSubtype = errors.New("unknown handshake subtype")
ErrMissingContent = errors.New("expected handshake content but message was empty")
ErrUnexpectedContent = errors.New("received unexpected handshake content")
ErrInvalidRemoteIndex = errors.New("peer sent an invalid index in handshake payload")
ErrIndexAllocation = errors.New("failed to allocate local index")
ErrNoCredential = errors.New("no handshake credential available for cert version")
ErrAsymmetricCipherKeys = errors.New("noise produced only one cipher key")
ErrMultiMessageUnsupported = errors.New("multi-message handshake patterns are not yet supported by the manager")
ErrSubtypeMismatch = errors.New("packet subtype does not match handshake machine subtype")
)
View on GitHub (pinned to dd8f660c0a)
Solutions
- Verify both peers use a handshake pattern whose message count matches the implementation's expectations
- Upgrade the library in case of a Noise key-derivation bug in your version
- Restart the handshake with a fresh Machine; never reuse partially completed machines
Example fix
// before: pattern declared with fewer messages than needed pattern := handshake.PatternIK1 // only 1 message; final stage has no 2nd key // after pattern := handshake.PatternIK // full pattern produces both cipher keys
Defensive patterns
Strategy: try-catch
Validate before calling
if patternMsgCount(pattern) != expectedStages {
return fmt.Errorf("pattern %v produces asymmetric keys: wrong stage count", pattern)
} Type guard
func keysSymmetric(res handshake.CipherResults) bool {
return res.EKey != nil && res.DKey != nil
} Try / catch
res, _, err := m.ProcessPacket(out, pkt)
if errors.Is(err, handshake.ErrAsymmetricCipherKeys) {
// key derivation is broken: abort and restart from a fresh machine
conn.Close()
log.Printf("noise produced asymmetric cipher keys with %s", conn.RemoteAddr())
return
} Prevention
- Use only vetted, complete handshake patterns with matching message counts on both peers
- Never reuse a Machine across handshakes — corrupted state can derail key derivation
- Upgrade promptly when the library patches Noise derivation bugs
When it happens
Trigger: In ProcessPacket, after the final handshake stage, eKey == nil || dKey == nil (handshake/machine.go:249) or ek == nil || dk == nil (machine.go:267) — NoiseCipherResults returned fewer than two cipher keys.
Common situations: Bug or mismatch in the handshake pattern implementation (wrong message count for the chosen pattern); peers using different pattern definitions so one side finalizes too early; corrupted handshake state from reusing a machine.
Related errors
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/13da12a01cb9ffe1.
Report an issue: GitHub.