{"record":{"id":"7d4dd0f687466a11","repo":"linera-io/linera-protocol","slug":"certificate-justification-commitment-does-not-matc","errorCode":null,"errorMessage":"Certificate justification commitment does not match its justification chain","messagePattern":"Certificate justification commitment does not match its justification chain","errorType":"validation","errorClass":"ChainError","httpStatus":null,"severity":"critical","filePath":"linera-chain/src/certificate/lite.rs","lineNumber":159,"sourceCode":"            signatures,\n        ))\n    }\n\n    /// Verifies the certificate: its signatures, its justification chain, and that the signed\n    /// unlocking round and first-round attestation are bound to that chain exactly as\n    /// [`ValidatedBlockCertificate::check`] and [`ConfirmedBlockCertificate::check`] require. This\n    /// is the single verification the worker applies to the certificate a retry proposal carries,\n    /// so it must reject a stripped or mismatched chain, not just check the pieces in isolation.\n    ///\n    /// [`ValidatedBlockCertificate::check`]: super::ValidatedBlockCertificate::check\n    /// [`ConfirmedBlockCertificate::check`]: super::ConfirmedBlockCertificate::check\n    pub fn check(&self, committee: &Committee) -> Result<&LiteValue, ChainError> {\n        // The carried chain's links are not signature-checked: the signed justification\n        // commitment is the hash-linked head of the chain, so the single signature check over\n        // this certificate's own quorum (below) attests every link — each link's voters verified\n        // the quorum beneath them before signing over its hash.\n        let derived_commitment = self.justification.verify(self.value.value_hash)?;\n        ensure!(\n            self.justification_commitment == derived_commitment,\n            ChainError::JustificationCommitmentMismatch\n        );\n        let value = VoteValue(\n            self.value.value_hash,\n            self.round,\n            self.value.kind,\n            self.unlocking_round,\n            self.first_round,\n            self.justification_commitment,\n        );\n        check_signatures(&value, &self.signatures, committee)?;\n        let top = self.justification.top_unlocking_round();\n        match self.value.kind {\n            CertificateKind::Validated => {\n                // The signed unlocking round must be the top of the chain, which must lie strictly\n                // below the certified round.\n                ensure!(","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/certificate/lite.rs#L141-L177","documentation":"LiteCertificate::check verifies a certificate's carried justification chain against the commitment its quorum signed: justification.verify(value_hash) recomputes the hash-linked head commitment of the carried chain, and linera-chain/src/certificate/lite.rs:159 requires it equals the certificate's justification_commitment field. A mismatch means the attached chain is not the one the validators committed to — stripped, replaced, or reordered links. Per the doc comment, this single check is what lets the worker trust a retry proposal's chain, so any mismatch is fatal for the certificate.","triggerScenarios":"A certificate whose justification chain was stripped down (links removed to save space) while the signed justification_commitment still refers to the full chain; a malicious or buggy validator signing one chain but attaching another; deserialization/version skew that reconstructs the chain with different link ordering; network peer relaying a certificate assembled from mismatched parts.","commonSituations":"Interoperability between client versions that serialize justification chains differently; a relay/proxy truncating large certificate payloads; consensus attacks replaying old justification chains under new certificates; corrupted storage of certificates.","solutions":["Treat the certificate as invalid: drop it and re-request the certificate (with its full original justification) from the source validator.","Verify version compatibility between the sender and receiver — a chain serialized by a different linera version may hash differently.","If producing certificates yourself, always attach justification via full_justification() so the chain and commitment are derived from the same data.","Audit custom relay code for any code path that reconstructs or prunes chains instead of forwarding certificates verbatim."],"exampleFix":"// before (relay reconstructing chains)\nlet cert = LiteCertificate::new_with_payload(value, round, ur, fr, old_commitment, signatures);\n\n// after (forward verbatim; derive chain and commitment together)\nlet cert = original_cert.clone();\ncert.check(&committee)?;","handlingStrategy":"try-catch","validationCode":"// Cheap pre-check before full verification: recompute the commitment of the carried chain.\nlet derived = certificate.justification.verify(certificate.value.value_hash)?;\nif derived != certificate.justification_commitment {\n    tracing::warn!(\"justification commitment mismatch; requesting original certificate\");\n    return request_certificate_from_validator(origin).await;\n}","typeGuard":null,"tryCatchPattern":"match certificate.check(&committee) {\n    Ok(value) => value,\n    Err(ChainError::JustificationCommitmentMismatch) => {\n        // Chain was stripped/replaced relative to what was signed: unusable.\n        tracing::warn!(\"certificate carries a mismatched justification chain; discarding\");\n        self.request_full_certificate(certificate.value.value_hash).await?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Forward certificates verbatim through relays and storage; never rebuild or prune chains.","When constructing certificates, derive chain and commitment from the same source (full_justification).","Keep linera versions aligned across the network so serialization hashes agree.","Fail closed on any certificate inconsistency: re-request, do not repair."],"tags":["linera","consensus","certificate","byzantine-fault-tolerance","rust"],"backgroundTag":"consensus-certificate-invalid","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}