linera-io/linera-protocol · error · ChainError
Signatures in a certificate must be from different validator
Error message
Signatures in a certificate must be from different validators
What it means
SignatureAggregator::append (linera-chain/src/data_types/mod.rs:1093-1129) rejects a second signature from a validator key already recorded in used_validators (chain.rs 1110-1114): each validator may contribute at most one signature per certificate, so quorum weight comes only from distinct validators. The same guard exists in check_signatures (data_types/mod.rs:1150) when verifying finished certificates, so any certificate containing duplicate signers is invalid on its face — double-counted weight could fake a quorum.
Source
Thrown at linera-chain/src/data_types/mod.rs:1111
pub fn append(
&mut self,
public_key: ValidatorPublicKey,
signature: ValidatorSignature,
) -> Result<Option<GenericCertificate<T>>, ChainError>
where
T: CertificateValue,
{
let hash_and_round = VoteValue(
self.partial.hash(),
self.partial.round,
T::KIND,
self.partial.unlocking_round(),
self.partial.first_round(),
self.partial.justification_commitment(),
);
signature.check(&hash_and_round, public_key)?;
// Check that each validator only appears once.
ensure!(
!self.used_validators.contains(&public_key),
ChainError::CertificateValidatorReuse
);
self.used_validators.insert(public_key);
// Update weight.
let voting_rights = self.committee.weight(&public_key);
ensure!(voting_rights > 0, ChainError::InvalidSigner);
self.weight += voting_rights;
// Update certificate.
self.partial.add_signature((public_key, signature));
if self.weight >= self.committee.quorum_threshold() {
self.weight = 0; // Prevent from creating the certificate twice.
Ok(Some(self.partial.clone()))
} else {
Ok(None)
}
}View on GitHub (pinned to 6c226ddcb3)
Solutions
- Deduplicate votes by ValidatorPublicKey before appending — keep the first (or freshest) vote per key in a BTreeMap/HashSet keyed merge
- Ignore duplicate vote deliveries at the network/handler layer instead of forwarding them to the aggregator
- When this appears in certificate verification (check_signatures), treat it as Byzantine evidence: reject the certificate and report the signer
Example fix
// before: appending every vote as it arrives
for vote in incoming_votes {
aggregator.append(vote.public_key, vote.signature).await?; // may hit reuse
}
// after: dedupe by validator key first
let mut seen = HashSet::new();
for vote in incoming_votes {
if seen.insert(vote.public_key) {
aggregator.append(vote.public_key, vote.signature).await?;
}
} Defensive patterns
Strategy: validation
Validate before calling
// Deduplicate votes by validator key BEFORE aggregating (data_types/mod.rs:1093):
let mut by_validator: BTreeMap<ValidatorPublicKey, ValidatorSignature> = BTreeMap::new();
for (key, sig) in incoming_votes {
by_validator.entry(key).or_insert(sig); // first vote per validator wins
}
for (key, sig) in by_validator {
if let Some(cert) = aggregator.append(key, sig)? {
return Ok(cert);
}
} Type guard
fn has_unique_signers(signatures: &[(ValidatorPublicKey, ValidatorSignature)]) -> bool {
let mut seen = HashSet::new();
signatures.iter().all(|(k, _)| seen.insert(*k))
} Try / catch
match aggregator.append(key, sig) {
Err(ChainError::CertificateValidatorReuse) => {
// duplicate delivery of an already-counted vote: skip silently — it carries
// no extra weight; do NOT count it toward the quorum
}
other => other?,
} Prevention
- Merge vote collections keyed by ValidatorPublicKey, never by concatenation
- Drop duplicate vote messages at the handler layer
- Treat finished certificates with duplicate signers as Byzantine evidence
When it happens
Trigger: Feeding the same validator's vote twice into the aggregator — duplicated network messages, rebroadcasts from different proxies, retry logic re-appending votes; aggregating from multiple sources without deduplicating by public key; verifying a Byzantine-forged certificate with repeated keys.
Common situations: Vote gossip forwarding the same vote via several paths; proxy nodes echoing messages; aggregator code that merges vote collections with concatenation instead of keying by ValidatorPublicKey.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- CannotRejectMessage
- Certificate justification commitment does not match its just
- Certificate unlocking round does not match the top of its ju
- Justification chain must lie in rounds strictly below the ce
- Certificate carries the first-round attestation but was not
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/6df80362e67f8bdf.
Report an issue: GitHub.