{"record":{"id":"8498961cf0f94add","repo":"diem/diem","slug":"decryptionerror","errorCode":null,"errorMessage":"DecryptionError: {}","messagePattern":"DecryptionError: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"network/src/noise/stream.rs","lineNumber":194,"sourceCode":"                        &self.buffers.read_buffer[*offset..(*offset + bytes_to_copy)],\n                    );\n                    trace!(\n                        \"CopyDecryptedFrame: copied {}/{} bytes\",\n                        *offset + bytes_to_copy,\n                        decrypted_len\n                    );\n                    *offset += bytes_to_copy;\n                    if *offset == decrypted_len as usize {\n                        self.read_state = ReadState::Init;\n                    }\n                    return Poll::Ready(Ok(bytes_to_copy));\n                }\n                ReadState::Eof(Ok(())) => return Poll::Ready(Ok(0)),\n                ReadState::Eof(Err(())) => {\n                    return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into()))\n                }\n                ReadState::DecryptionError(ref e) => {\n                    return Poll::Ready(Err(io::Error::new(\n                        io::ErrorKind::InvalidData,\n                        format!(\"DecryptionError: {}\", e),\n                    )))\n                }\n            }\n        }\n    }\n}\n\n//\n// Writing a stream\n// ----------------\n//\n\n/// Possible write states for a [`NoiseStream`]\n#[derive(Debug)]\nenum WriteState {\n    /// Initial State","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/diem/diem/blob/fc4714a8ea273b6efe8b13dbce72ea60aad9a16c/network/src/noise/stream.rs#L176-L212","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before: keep retrying reads on a failed session\nloop { stream.read(&mut buf).await?; }\n// after: detect the invalid-data decryption failure and reconnect\nmatch stream.read(&mut buf).await {\n    Err(e) if e.kind() == io::ErrorKind::InvalidData\n        && e.to_string().starts_with(\"DecryptionError\") => {\n        drop(stream);\n        stream = reconnect_noise().await?;\n    }\n    r => return r,\n}","handlingStrategy":"try-catch","validationCode":"// Cannot be validated ahead of time — it fires during read. Detect and reconnect:\nasync fn read_frame(stream: &mut NoiseStream) -> io::Result<Vec<u8>> {\n    let mut buf = vec![0u8; MAX_FRAME];\n    match stream.read(&mut buf).await {\n        Err(e) if e.kind() == io::ErrorKind::InvalidData\n            && e.to_string().starts_with(\"DecryptionError:\") => {\n            Err(e) // caller must drop and re-handshake; session is unrecoverable\n        }\n        r => r.map(|n| buf[..n].to_vec()),\n    }\n}","typeGuard":"fn is_decryption_io_error(e: &io::Error) -> bool {\n    e.kind() == io::ErrorKind::InvalidData\n        && e.to_string().starts_with(\"DecryptionError:\")\n}","tryCatchPattern":"match stream.read(&mut buf).await {\n    Err(e) if is_decryption_io_error(&e) => {\n        error!(\"decryption failed; tearing down noise session and reconnecting\");\n        drop(stream);\n        stream = reestablish_noise_connection().await?;\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["noise","stream","decryption","aead","network"],"backgroundTag":"decryption-failed","analyzedSha":"fc4714a8ea273b6efe8b13dbce72ea60aad9a16c","analyzedAt":"2026-09-04T21:07:05.890Z","contentChangedAt":"2026-09-04T21:07:05.890Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}