{"record":{"id":"f31033f53a3933ed","repo":"linera-io/linera-protocol","slug":"justificationcommitmentmismatch","errorCode":"JustificationCommitmentMismatch","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":"error","filePath":"linera-chain/src/justification/mod.rs","lineNumber":614,"sourceCode":"        }\n    }\n}\n\n/// Checks that `opening` is a quorum an honest voter could cite from a vote of the given kind,\n/// round and unlocking round for the block with the given header: it validates the same block, in\n/// the round the vote's payload grounds on, and its signatures form a genuine quorum of\n/// `committee` over the reconstructed `ValidatedBlock` payload. These are exactly the checks a\n/// voter performs before signing the opening's commitment, so their failure on a signed opening\n/// convicts the signer.\nfn check_cited_quorum(\n    header: &BlockHeader,\n    round: Round,\n    kind: CertificateKind,\n    unlocking_round: Option<Round>,\n    opening: &CommittedQuorum,\n    committee: &Committee,\n) -> Result<(), ChainError> {\n    ensure!(\n        opening.value_hash == CryptoHash::new(header),\n        ChainError::JustificationCommitmentMismatch\n    );\n    match kind {\n        // A validated vote cites the quorum grounding its unlocking round, in a lower round.\n        CertificateKind::Validated => ensure!(\n            unlocking_round == Some(opening.round) && opening.round < round,\n            ChainError::JustificationUnlockingRoundMismatch\n        ),\n        // A confirmed vote cites the quorum that validated the block in the same round.\n        CertificateKind::Confirmed => ensure!(\n            opening.round == round,\n            ChainError::JustificationUnlockingRoundMismatch\n        ),\n        // Timeout votes cite nothing; any commitment is dishonest.\n        CertificateKind::Timeout => ensure!(false, ChainError::JustificationCommitmentMismatch),\n    }\n    // A quorum with an unlocking round cites a quorum itself, and vice versa: its own commitment","sourceCodeStart":596,"sourceCodeEnd":632,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/justification/mod.rs#L596-L632","documentation":"Thrown by check_cited_quorum when the opening quorum's value_hash does not equal the hash of the block header the vote was for (ChainError::JustificationCommitmentMismatch). A vote's justification commitment signs an opening that must validate the same block the vote is about; an opening over a different block cannot ground the vote. This is one of the specific reasons an InvalidJustification equivocation proof is considered genuine — a validator signing a commitment to an opening about a different block is at fault.","triggerScenarios":"Calling check_cited_quorum (directly, or via EquivocationProof::check on an InvalidJustification proof) where opening.value_hash != CryptoHash::new(header). In honest certificate flow this appears if a voter cites a justification chain for a different block; in proof flow it is the expected condition that convicts the signer.","commonSituations":"Voters copying a justification from a different block's context; certificate-building code pairing headers with the wrong CommittedQuorum; slashing evidence where the mismatch is the fault being proven.","solutions":["If you are a voter: only sign a justification commitment computed over the same block you are voting on (recompute chain.commitment(CryptoHash::new(&header))).","If you are verifying: treat this mismatch as attributable misbehavior — report an InvalidJustication proof with the opening as evidence.","If building certificates, derive the opening from the block's own validation path, never from a cached chain of another block.","In tests, ensure headers and openings are generated from the same block."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"ensure!(\n    opening.value_hash == CryptoHash::new(&header),\n    \"justification opens a different block than the vote is for\"\n);","typeGuard":null,"tryCatchPattern":"match check_cited_quorum(header, round, kind, unlocking_round, &opening, committee) {\n    Err(ChainError::JustificationCommitmentMismatch) => {\n        // attributable fault: build an InvalidJustification proof with this opening\n        let proof = EquivocationProof::InvalidJustification { validator, header: header.clone(), round, kind, unlocking_round, first_round, signature, opening };\n        report_fault(proof);\n        Err(anyhow::anyhow!(\"voter cited a justification for a different block\"))\n    }\n    other => other,\n}","preventionTips":["As a voter, compute the justification commitment over your own block's hash: chain.commitment(CryptoHash::new(&header)).","Never reuse a justification chain from another block.","Bind headers and openings together at construction time in the same function."],"tags":["consensus","justification","certificate-verification","fault-attribution"],"backgroundTag":"justification-commitment-mismatch","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}