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 State

View on GitHub (pinned to fc4714a8ea)

Solutions

  1. Tear down and re-establish the connection; the Noise session is unrecoverable after an AEAD failure
  2. Check for anything between the peers (proxy, VPN, NIC offload bug) that could corrupt or reorder stream bytes
  3. Verify both ends run compatible versions of the Noise stream framing code
  4. 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

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


AI-assisted analysis of diem/diem@fc4714a8ea (2026-09-04). Data as JSON: /api/errors/8498961cf0f94add. Report an issue: GitHub.