diem/diem · critical
DecryptionError: {}
Error message
DecryptionError: {} What it means
Raised in NoiseStream::poll_read when a frame received on the Noise-encrypted stream fails decryption. The stream stores the NoiseError in ReadState::DecryptionError and then surfaces it as an io::Error with ErrorKind::InvalidData and the message 'DecryptionError: {}'. After this the stream is permanently failed — subsequent reads return UnexpectedEof, so the connection cannot recover and must be torn down and re-established.
Source
Thrown at network/src/noise/stream.rs:194
&self.buffers.read_buffer[*offset..(*offset + bytes_to_copy)],
);
trace!(
"CopyDecryptedFrame: copied {}/{} bytes",
*offset + bytes_to_copy,
decrypted_len
);
*offset += bytes_to_copy;
if *offset == decrypted_len as usize {
self.read_state = ReadState::Init;
}
return Poll::Ready(Ok(bytes_to_copy));
}
ReadState::Eof(Ok(())) => return Poll::Ready(Ok(0)),
ReadState::Eof(Err(())) => {
return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into()))
}
ReadState::DecryptionError(ref e) => {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("DecryptionError: {}", e),
)))
}
}
}
}
}
//
// Writing a stream
// ----------------
//
/// Possible write states for a [`NoiseStream`]
#[derive(Debug)]
enum WriteState {
/// Initial StateView on GitHub (pinned to fc4714a8ea)
Solutions
- Tear down and re-establish the connection; the Noise session is unrecoverable after an AEAD failure
- Check for anything between the peers (proxy, VPN, NIC offload bug) that could corrupt or reorder stream bytes
- Verify both ends run compatible versions of the Noise stream framing code
- If unexplained and repeated, investigate for active tampering on the network path
Example fix
// before: keep retrying reads on a failed session
loop { stream.read(&mut buf).await?; }
// after: detect the invalid-data decryption failure and reconnect
match stream.read(&mut buf).await {
Err(e) if e.kind() == io::ErrorKind::InvalidData
&& e.to_string().starts_with("DecryptionError") => {
drop(stream);
stream = reconnect_noise().await?;
}
r => return r,
} Defensive patterns
Strategy: try-catch
Validate before calling
// Cannot be validated ahead of time — it fires during read. Detect and reconnect:
async fn read_frame(stream: &mut NoiseStream) -> io::Result<Vec<u8>> {
let mut buf = vec![0u8; MAX_FRAME];
match stream.read(&mut buf).await {
Err(e) if e.kind() == io::ErrorKind::InvalidData
&& e.to_string().starts_with("DecryptionError:") => {
Err(e) // caller must drop and re-handshake; session is unrecoverable
}
r => r.map(|n| buf[..n].to_vec()),
}
} Type guard
fn is_decryption_io_error(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::InvalidData
&& e.to_string().starts_with("DecryptionError:")
} Try / catch
match stream.read(&mut buf).await {
Err(e) if is_decryption_io_error(&e) => {
error!("decryption failed; tearing down noise session and reconnecting");
drop(stream);
stream = reestablish_noise_connection().await?;
}
other => other?,
} Prevention
- Never reuse a stream after a decryption error — later reads return UnexpectedEof
- Investigate any middleboxes/VPN/NIC offload that could corrupt stream bytes
- Treat repeated AEAD failures on a path as a possible tampering indicator
- Keep Noise stream framing code versions in sync on both peers
When it happens
Trigger: Reading a decrypted frame whose AEAD tag fails to verify: ciphertext corrupted in transit, a bit-flip/tampering attempt, or the two ends' Noise sessions desynchronized (e.g. one side wrote garbage or frames got lost/interleaved).
Common situations: Faulty hardware or middlebox corrupting TCP payloads; an active attacker tampering with traffic; a protocol bug writing misaligned frames; connecting two incompatible stream implementations that share a port.
Related errors
- EncryptionError: {}
- noise server: client {0}: error parsing handshake init messa
- noise server: client {0}: known client peer id connecting to
- noise server: client {0}: client connecting with unauthentic
- noise server: client {0}: client's self-reported peer id and
AI-assisted analysis of diem/diem@fc4714a8ea (2026-09-04).
Data as JSON: /api/errors/8498961cf0f94add.
Report an issue: GitHub.