{"record":{"id":"c8b9930596eb81be","repo":"linera-io/linera-protocol","slug":"signatures-in-a-certificate-must-form-a-quorum","errorCode":null,"errorMessage":"Signatures in a certificate must form a quorum","messagePattern":"Signatures in a certificate must form a quorum","errorType":"validation","errorClass":"ChainError","httpStatus":null,"severity":"error","filePath":"linera-chain/src/data_types/mod.rs","lineNumber":1160,"sourceCode":"    signatures: &[(ValidatorPublicKey, ValidatorSignature)],\n    committee: &Committee,\n) -> Result<(), ChainError> {\n    // Check the quorum.\n    let mut weight = 0;\n    let mut used_validators = HashSet::new();\n    for (validator, _) in signatures {\n        // Check that each validator only appears once.\n        ensure!(\n            !used_validators.contains(validator),\n            ChainError::CertificateValidatorReuse\n        );\n        used_validators.insert(*validator);\n        // Update weight.\n        let voting_rights = committee.weight(validator);\n        ensure!(voting_rights > 0, ChainError::InvalidSigner);\n        weight += voting_rights;\n    }\n    ensure!(\n        weight >= committee.quorum_threshold(),\n        ChainError::CertificateRequiresQuorum\n    );\n    // All that is left is checking signatures!\n    ValidatorSignature::verify_batch(value, signatures.iter())?;\n    Ok(())\n}\n\nimpl BcsSignable<'_> for ProposalContent {}\n\nimpl BcsSignable<'_> for VoteValue {}\n\ndoc_scalar!(\n    MessageAction,\n    \"Whether an incoming message is accepted or rejected.\"\n);\n\n#[cfg(test)]","sourceCodeStart":1142,"sourceCodeEnd":1178,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/data_types/mod.rs#L1142-L1178","documentation":"Thrown by check_signatures when the summed voting weight of a certificate's signers is below the committee's quorum threshold (ChainError::CertificateRequiresQuorum). Even if every individual signature is valid, a certificate is only meaningful if it proves agreement of a quorum (by voting weight) of the committee. The check runs after per-validator membership checks and before batch signature verification.","triggerScenarios":"Verifying a certificate whose signature set aggregates less than committee.quorum_threshold() voting weight — e.g. a partial certificate captured before quorum was reached, a certificate checked against a larger committee than the one that signed it, or signatures pruned/lost in transit.","commonSituations":"Persisting or forwarding a partial (non-final) certificate from SignatureAggregator (append returned None and the partial state leaked); committee enlargement between epochs raising the threshold; network paths dropping some signatures; tests using too few validator keys.","solutions":["Ensure the certificate is only used after the aggregator reached quorum — SignatureAggregator::append returns Some(cert) exactly when the threshold is met.","Recollect the missing signatures from validators until quorum before verifying.","Verify against the committee (epoch) that produced the certificate, so the threshold matches the signing set.","If weight was lost to deduplication, confirm the removed entries really were duplicates and not distinct validators."],"exampleFix":"// before\nlet cert = aggregator.partial_clone_hack(); // partial, below quorum\ncheck_signatures(&value, &cert.signatures, &committee)?;\n\n// after\nlet mut aggregator = SignatureAggregator::new(value, round, unlocking, first_round, commitment, &committee);\nlet cert = loop {\n    let (key, sig) = receive_vote().await?;\n    if let Some(cert) = aggregator.append(key, sig)? {\n        break cert; // quorum reached\n    }\n};","handlingStrategy":"retry","validationCode":"let weight: u64 = signatures.iter().map(|(k, _)| committee.weight(k)).sum();\nif weight < committee.quorum_threshold() {\n    return Err(anyhow::anyhow!(\n        \"insufficient voting weight: {}/{}\",\n        weight,\n        committee.quorum_threshold()\n    ));\n}","typeGuard":null,"tryCatchPattern":"match check_signatures(&value, &signatures, committee) {\n    Err(ChainError::CertificateRequiresQuorum) => {\n        // collect more signatures from remaining validators, then retry\n        collect_missing_signatures(&mut signatures, committee).await?;\n        check_signatures(&value, &signatures, committee)\n    }\n    other => other,\n}","preventionTips":["Only circulate a certificate after SignatureAggregator::append returns Some.","Track accumulated weight while collecting votes and request more before verification.","Verify against the producing epoch's committee so thresholds match."],"tags":["consensus","quorum","certificate-verification","committee"],"backgroundTag":"quorum-not-reached","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}