This guide explains the comprehensive validation system that ensures election integrity in the Blockchain Voting System.
The system uses multi-layer validation to prevent invalid votes:
┌─────────────────────────────────────┐
│ Layer 1: Field Validation │ Vote.isValid()
├─────────────────────────────────────┤
│ Layer 2: Format Validation │ VoteValidator
├─────────────────────────────────────┤
│ Layer 3: Duplicate Detection │ Blockchain.hasVoterVoted()
├─────────────────────────────────────┤
│ Layer 4: Block-Level Check │ Block.hasVoter()
├─────────────────────────────────────┤
│ Layer 5: Hash Integrity │ Block.isValid()
└─────────────────────────────────────┘
- Voter ID is not empty
- Candidate name is not empty
- Fields don't contain only whitespace
Vote::isValid() in Vote.cpp
✅ Vote("VOTER-001", "Alice Johnson")
✅ Vote("ABC123", "Bob Smith")
✅ Vote("temp_voter_42", "Charlie Davis")❌ Vote("", "Alice Johnson") // Empty voter ID
❌ Vote("VOTER-001", "") // Empty candidate
❌ Vote(" ", "Bob Smith") // Whitespace-only voter ID
❌ Vote("VOTER-002", " ") // Whitespace-only candidate✗ Vote rejected: Invalid vote data (empty fields)
- Minimum length: 3 characters
- Maximum length: 50 characters
- Allowed characters:
- Alphanumeric (A-Z, a-z, 0-9)
- Hyphen (-)
- Underscore (_)
- Space ( )
- Minimum length: 2 characters
- Maximum length: 100 characters
- Allowed characters: Same as voter ID
VoteValidator::validateVote() in VoteValidator.cpp
✅ VoteValidator::validateVoterID("ABC") // Min length
✅ VoteValidator::validateVoterID("VOTER-123") // With hyphen
✅ VoteValidator::validateVoterID("temp_user_001") // With underscore
✅ VoteValidator::validateCandidate("Jo") // Min length
✅ VoteValidator::validateCandidate("Alice Johnson") // With space❌ VoteValidator::validateVoterID("AB") // Too short
❌ VoteValidator::validateVoterID("A!B") // Invalid char
❌ VoteValidator::validateVoterID("") // Empty
❌ VoteValidator::validateCandidate("X") // Too short
❌ VoteValidator::validateCandidate("Alice@Email") // Invalid char✗ Voter ID too short (minimum 3 characters)
✗ Voter ID too long (maximum 50 characters)
✗ Voter ID contains invalid characters
✗ Candidate name too short (minimum 2 characters)
✗ Candidate name too long (maximum 100 characters)
- Scans entire blockchain for duplicate voter IDs
- Prevents the same voter from voting multiple times
- Critical security feature for election integrity
Blockchain::hasVoterVoted() in Blockchain.cpp
1. Receive new vote with voter ID "VOTER-001"
2. Scan Genesis Block (Block #0) - No votes
3. Scan Block #1 - Check all votes for "VOTER-001"
4. Scan Block #2 - Check all votes for "VOTER-001"
↓ Found! "VOTER-001" already voted in Block #2
5. REJECT the new vote- Current: O(n × m) where n=blocks, m=votes/block
- Optimized: O(1) with hash set (see COMPLEXITY_ANALYSIS.md)
Block #1:
Vote("VOTER-001", "Alice Johnson") ✅ Accepted (first vote)
Vote("VOTER-002", "Bob Smith") ✅ Accepted (first vote)Block #2 (Later):
Vote("VOTER-001", "Charlie Davis") ❌ REJECTED - Already voted in Block #1
Vote("VOTER-003", "Alice Johnson") ✅ Accepted (first vote)✗ Vote rejected: Voter VOTER-001 has already voted in the blockchain
- Prevents duplicate voter IDs within the same pending block
- Catches attempts to vote twice before block is sealed
Block::hasVoter() in Block.cpp
Pending Block #5:
pendingBlock.addVote(Vote("VOTER-100", "Alice")) ✅ Accepted
pendingBlock.addVote(Vote("VOTER-101", "Bob")) ✅ Accepted
pendingBlock.addVote(Vote("VOTER-100", "Charlie")) ❌ REJECTED - Duplicate in this block!✗ Vote rejected: Voter VOTER-100 already voted in this pending block
- Block's stored hash matches its calculated hash
- Detects any tampering with block data
- Ensures cryptographic integrity
Block::isValid() in Block.cpp
1. Get stored hash: "a1b2c3..."
2. Recalculate hash from current block data
3. Compare: stored == calculated?
✅ Match → Block is valid
❌ Mismatch → Block tampered!// Original block
Block block1(1, "0");
block1.addVote(Vote("VOTER-001", "Alice"));
// Hash: a1b2c3d4e5f6...
// Someone tries to modify the vote (after adding to chain)
// Change "Alice" to "Bob"
block1.isValid(); // ❌ Returns false - hash mismatch!✗ Block #2 has invalid hash!
Vote newVote("VOTER-123", "Alice Johnson");
Block pendingBlock(5, previousHash);
Blockchain chain;
// Step 1: Field Validation
if (!newVote.isValid()) {
return; // ❌ REJECT: Empty fields
}
// Step 2: Format Validation
ValidationResult result = VoteValidator::validateVote(
newVote.getVoterTempID(),
newVote.getCandidate()
);
if (!result.isValid) {
std::cerr << result.errorMessage;
return; // ❌ REJECT: Invalid format
}
// Step 3: Cross-Chain Duplicate Check
if (chain.hasVoterVoted("VOTER-123")) {
return; // ❌ REJECT: Already voted in blockchain
}
// Step 4: Block-Level Duplicate Check
if (pendingBlock.hasVoter("VOTER-123")) {
return; // ❌ REJECT: Already in this block
}
// Step 5: Add Vote (triggers hash recalculation)
pendingBlock.addVote(newVote); // ✅ ACCEPTED
// Step 6: Seal Block and Add to Chain
chain.addBlock(pendingBlock);
// Step 7: Validate Chain Integrity
if (chain.isChainValid()) {
// ✅ SUCCESS: Vote is now permanently recorded
}✅ Normal vote: Vote("VOTER-001", "Alice Johnson")
✅ Minimum length: Vote("ABC", "Jo")
✅ With special chars: Vote("VOTER_123-A", "Bob Smith-Jr")
✅ Maximum length: Vote("X" * 50, "Y" * 100)❌ Empty voter ID: Vote("", "Alice")
❌ Empty candidate: Vote("VOTER-001", "")
❌ Whitespace voter: Vote(" ", "Alice")
❌ Whitespace candidate: Vote("VOTER-001", " ")
❌ Too short voter: Vote("AB", "Alice")
❌ Too short candidate: Vote("VOTER-001", "A")
❌ Invalid characters: Vote("VOTER@001", "Alice")// Block #1
✅ Vote("VOTER-001", "Alice")
// Block #2
❌ Vote("VOTER-001", "Bob") // Duplicate voter ID
✅ Vote("VOTER-002", "Alice") // Different voter, same candidate OKBlock pendingBlock;
✅ Vote("VOTER-001", "Alice")
❌ Vote("VOTER-001", "Bob") // Same block duplicate
✅ Vote("VOTER-002", "Alice")Block block1(1, "0");
block1.addVote(Vote("VOTER-001", "Alice"));
✅ block1.isValid() // Returns true
// Tamper with vote (simulate attack)
block1.votes[0].candidate = "Bob";
❌ block1.isValid() // Returns false - hash mismatchmake test
# or
./voting_system============================================================
TEST 1: Valid Votes
============================================================
✓ Vote accepted: VOTER-A1B2C3 -> Alice Johnson
✓ Vote accepted: VOTER-D4E5F6 -> Bob Smith
✓ All valid votes accepted successfully
============================================================
TEST 2: Invalid Vote Fields
============================================================
✗ Vote rejected: Invalid vote data (empty fields)
✗ Vote rejected: Invalid vote data (empty fields)
✗ All invalid votes correctly rejected
============================================================
TEST 3: Duplicate Vote Detection
============================================================
✓ Vote accepted: VOTER-001 -> Alice Johnson
✓ Vote accepted: VOTER-002 -> Bob Smith
✗ Vote rejected: Voter VOTER-001 has already voted in the blockchain
✓ Duplicate detection working correctly
✅ Empty Votes: Cannot cast vote without voter ID or candidate
✅ Double Voting: One voter ID = one vote maximum
✅ Malformed Data: Length and character restrictions
✅ Data Tampering: Hash verification detects modifications
✅ Replay Attacks: Timestamps and chain position prevent reuse
✅ Anonymous Voting: Temporary IDs don't reveal identity
✅ Multiple Votes for Same Candidate: Different voters can choose same candidate
✅ Immutable Audit Trail: Complete voting history preserved
✅ Transparent Verification: Anyone can validate the chain
- Always validate before adding: Use
addVoteToPendingBlock()instead of directaddVote() - Check return values: Methods return
boolto indicate success/failure - Validate entire chain: Call
isChainValid()periodically - Log rejections: Track invalid votes for security auditing
- Monitor rejection rate: High rejection rate may indicate attack
- Regular chain validation: Ensure integrity hasn't been compromised
- Backup blockchain: Immutable data should be preserved
- Review validation rules: Adjust length limits based on requirements
Edit VoteValidator.h:
class VoteValidator {
public:
// Customize these values
static const size_t MIN_VOTER_ID_LENGTH = 5; // Changed from 3
static const size_t MAX_VOTER_ID_LENGTH = 100; // Changed from 50
static const size_t MIN_CANDIDATE_LENGTH = 3; // Changed from 2
static const size_t MAX_CANDIDATE_LENGTH = 200; // Changed from 100
};Add to VoteValidator.cpp:
bool VoteValidator::validateCustomRule(const std::string& voterId) {
// Example: Must start with "VOTER-"
if (voterId.substr(0, 6) != "VOTER-") {
return false;
}
return true;
}Issue: "Vote rejected but looks valid"
// Check for hidden whitespace
Vote vote("VOTER-001 ", "Alice"); // Trailing space!Solution: Trim input before creating Vote
std::string trimmed = trim(input); // Remove leading/trailing whitespace
Vote vote(trimmed, candidate);Issue: "Duplicate rejection seems wrong"
// Case sensitivity matters!
Vote("VOTER-001", "Alice") // Different from
Vote("voter-001", "Alice") // Different voter IDSolution: Normalize voter IDs (e.g., uppercase)
| Operation | Time | Notes |
|---|---|---|
| Field validation | <1μs | String checks |
| Format validation | <10μs | Length + character checks |
| Duplicate check (current) | O(n×m) | Scans full chain |
| Duplicate check (optimized) | O(1) | With hash set |
| Hash validation | <1ms | SHA-256 calculation |
For elections with >1,000 voters, implement hash set optimization (see COMPLEXITY_ANALYSIS.md) to reduce duplicate checking from O(n×m) to O(1).
The multi-layer validation system ensures:
- ✅ Data integrity through field and format validation
- ✅ Election integrity through duplicate detection
- ✅ Cryptographic security through hash verification
- ✅ Transparency through clear error messages
This comprehensive approach makes the blockchain voting system secure and reliable for real-world elections.