slackhq/nebula · error
noise WriteMessage: %w
Error message
noise WriteMessage: %w
What it means
Wraps an error from noiseprotocol's WriteMessage during handshake response construction. WriteMessage encrypts the handshake payload and derives the data-cipher keys; a failure means the Noise state machine rejected the step (bad state, message too large, or internal crypto failure).
Source
Thrown at handshake/machine.go:450
header.Encode(
out[start:],
header.Version, header.Handshake, m.subtype,
m.result.RemoteIndex,
uint64(m.hs.MessageIndex()+1),
)
// noise.WriteMessage appends the encrypted handshake message to out,
// reusing capacity when present.
//
// The (dKey, eKey) ordering here is correct for IX, where the responder
// completes the handshake by writing the stage-2 message. noise returns
// (cs1, cs2) where cs1 is the initiator->responder cipher (which is the
// responder's decrypt key). For 3-message patterns where an initiator
// finishes by writing the final message, this ordering would be wrong;
// revisit when XX/pqIX lands.
out, dKey, eKey, err := m.hs.WriteMessage(out, hsBytes)
if err != nil {
return nil, nil, nil, fmt.Errorf("noise WriteMessage: %w", err)
}
return out, dKey, eKey, nil
}
View on GitHub (pinned to dd8f660c0a)
Solutions
- Discard the handshake machine and start a fresh Initiate/handshake from state 0
- Check that ProcessPacket/Initiate are not called concurrently on the same machine (serialize with a lock)
- Inspect the wrapped noise error for message-index/state mismatch and ensure messages are processed in order
- Upgrade nebula/noise library if the pattern requires more message space (see comment about 3-message patterns)
Example fix
// before
out, dKey, eKey, err := m.hs.WriteMessage(out, hsBytes)
// after: recover by resetting the handshake
out, dKey, eKey, err := m.hs.WriteMessage(out, hsBytes)
if err != nil {
// caller should rebuild: m = NewMachine(...) and retry handshake
return nil, nil, nil, fmt.Errorf("noise WriteMessage: %w", err)
} Defensive patterns
Strategy: retry
Validate before calling
if m.hs == nil {
return errors.New("handshake state missing; re-initiate")
} Try / catch
out, dKey, eKey, err := buildResponse(...)
if err != nil {
// reset handshake state and retry once
m = NewMachine(subtype, ...)
out, dKey, eKey, err = buildResponse(...)
if err != nil { return fmt.Errorf("handshake failed after retry: %w", err) }
} Prevention
- Never share a handshake Machine across goroutines without locking
- Process handshake messages strictly in order
- Discard and rebuild machines after any noise error instead of reusing them
When it happens
Trigger: Calling buildResponse (from Initiate or ProcessPacket) when m.hs.WriteMessage fails because the handshake state is not at the expected message index, hsBytes exceeds Noise max message size, or the cipher state is invalid.
Common situations: Replaying/corrupting handshake state after a failed earlier step; concurrently calling Initiate/ProcessPacket on the same machine; oversized payload; noise library version mismatch.
Related errors
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/0da23a5e9ea080f2.
Report an issue: GitHub.