Skip to main content

Overview

Ubu-Block implements a Byzantine Fault Tolerant (BFT) consensus mechanism designed for election data integrity. Unlike traditional blockchains that use Proof-of-Work (PoW), Ubu-Block uses authorized validators with digital signatures to achieve consensus.
BFT Tolerance: The system can tolerate up to 1/3 malicious nodes while maintaining integrity.

Why BFT for Elections?

Election systems have unique requirements:
RequirementBFT Solution
FinalityImmediate - no waiting for confirmations
EfficiencyNo computational waste on mining
AuthorityAuthorized validators only
TransparencyAll signatures are verifiable
SpeedFast block creation and validation

How It Works

1. Dynamic Signatories

Ubu-Block features a community-driven approach with dynamic validators:
// Public keys stored on-chain
pub struct PubKey {
    pub hash: String,               // SHA-256 hash of public key
    pub creator: String,            // Node identifier
    pub bytes: Vec<u8>,            // ECDSA P-256 public key
    pub state: String,              // Active (A) or Revoked (R)
    pub time_added: DateTime<Utc>, // When added to chain
    pub is_revoked: bool,          // Revocation status
    pub time_revoked: Option<DateTime<Utc>>,
    pub add_block_height: usize,   // Block when added
}

2. Cryptographic Signatures

Each block is signed using ECDSA (Elliptic Curve Digital Signature Algorithm) with the P-256 curve:
// From types/src/crypto.rs:12
pub fn sign_hash(key: &SigningKey, hash: &str) -> String {
    let hash = hex::decode(hash).unwrap();
    let signature: Signature = key.sign(&hash);
    hex::encode(serialize(&signature).unwrap())
}

3. Signature Verification

Any node can verify a block’s authenticity:
// From database/src/lib.rs:272
let pub_key = self.get_public_key(&block.signature_pub_key_hash).await?;
let verifier: VerifyingKey = deserialize(&pub_key.bytes).unwrap();
let signature_bytes = hex::decode(&block.hash_signature).unwrap();
let signature: Signature = deserialize(&signature_bytes).unwrap();

// Verify signature matches block hash
verifier.verify(calculated_hash.as_bytes(), &signature).unwrap();

Block Creation Process

Step-by-Step Flow

Implementation

// From types/src/lib.rs:74
pub fn new(
    signer: &BlockSigner,        // (private_key, public_key, metadata)
    prev_hash: &str,              // Previous block hash
    results: Vec<CandidateResult>,// Election data
    height: usize,                // Block number
    merkle_root: [u8; 32],       // Merkle tree root
) -> Self {
    // Sign previous hash to prove chain continuity
    let prev_hash_signature = sign_hash(&signer.0, prev_hash);
    
    // Create signature public key hash for validator identification
    let sigkey_hash = sha256_digest(&signer.1);
    
    // Build block header
    let header = ElectionBlockHeader {
        previous_hash: hex::decode(prev_hash).unwrap()
            .as_slice().try_into().unwrap(),
        merkle_root,
        timestamp: Utc::now().timestamp() as i64,
        block_number: height as i64,
        validator_signature: sigkey_hash.clone(),
    };
    
    // Hash and sign the block
    let hash = crypto::hash_block(&header);
    let hash_signature = sign_hash(&signer.0, &hash);
    
    Self {
        hash,
        hash_signature,
        inner: BlockType::Result(results),
        height,
        merkle_root,
        timestamp: Utc::now(),
        prev_hash: prev_hash.to_string(),
        signature_pub_key_hash: sigkey_hash.to_string(),
        prev_hash_signature,
        version: VERSION,
        creator: signer.2.creator.clone(),
        creator_pub_key: sha256_digest(&signer.1),
    }
}

Validator Management

Adding Validators

New validators are added by storing their public keys on-chain:
// From database/src/lib.rs:93
pub async fn add_public_key(
    &self,
    pub_key: &[u8],
    creator: &str,
    pubkey_hash: &str,
    block_height: i32,
) -> Result<i64, sqlx::Error> {
    let sql = "INSERT INTO pubkeys(
        pubkey_hash, creator, pubkey, state, time_added, block_height
    ) VALUES (?, ?, ?, ?, ?, ?)";
    
    sqlx::query(sql)
        .bind(pubkey_hash)
        .bind(creator)
        .bind(hex::encode(pub_key))
        .bind("A")  // Active state
        .bind(Utc::now().timestamp())
        .bind(block_height)
        .execute(&mut *pool)
        .await
}

Key Features

  • On-Chain Storage: All public keys stored in blockchain
  • Immutable History: Can’t delete past keys, only revoke
  • Transparent: Anyone can see authorized validators
  • Auditable: Track when keys were added/revoked

Byzantine Fault Tolerance

The Byzantine Generals Problem

The classic problem: How do distributed nodes reach consensus when some may be:
  • Malicious: Sending false information
  • Faulty: Sending inconsistent information
  • Compromised: Acting under attacker control

Ubu-Block’s Solution

BFT consensus through:
  1. Cryptographic Proof: Digital signatures prove block origin
  2. Chain Validation: Each block references and signs previous hash
  3. Merkle Roots: Tamper-evident data structure
  4. Multiple Validators: Distributed trust across nodes
  5. Public Verification: Anyone can validate signatures

Fault Tolerance Math

For a network with n validators:
  • Honest nodes needed: 2f + 1 where f is faulty nodes
  • Maximum faults tolerated: f < n/3
  • Example: With 10 validators, up to 3 can be malicious
n = 10 validators
f = 3 malicious nodes tolerated
2f + 1 = 7 honest nodes needed for consensus
If more than 1/3 of validators are compromised, the network security is at risk. This is why validator selection and key management are critical.

Consensus Validation

Every node validates blocks independently:

Validation Steps

// From database/src/lib.rs:237
pub async fn is_valid(&self) -> Result<bool, sqlx::Error> {
    let height = self.get_height().await?;
    
    for index in 0..height {
        let block = self.get_block_by_height(index).await?;
        
        // 1. Verify chain linkage
        let prev_hash = if index == 0 {
            [0u8; 32]
        } else {
            self.get_block_by_height(index - 1)
                .await?
                .hash
                .as_bytes()
                .try_into()
                .unwrap()
        };
        
        // 2. Reconstruct header with stored merkle_root
        let header = ElectionBlockHeader {
            block_number: block.height as i64,
            merkle_root: block.merkle_root,
            previous_hash: prev_hash,
            validator_signature: block.signature_pub_key_hash.clone(),
            timestamp: block.timestamp.timestamp() as i64,
        };
        
        // 3. Re-hash header and compare
        let calculated_hash = crypto::hash_block(&header);
        if calculated_hash != block.hash {
            return Err(sqlx::Error::Protocol(
                format!("Block hash mismatch at index {}", index).into(),
            ));
        }
        
        // 4. Verify signature
        let pub_key = self.get_public_key(&block.signature_pub_key_hash).await?;
        let verifier: VerifyingKey = deserialize(&pub_key.bytes).unwrap();
        let signature: Signature = deserialize(&signature_bytes).unwrap();
        verifier.verify(calculated_hash.as_bytes(), &signature).unwrap();
        
        // 5. Verify chain linkage
        if block.prev_hash != hex::encode(prev_hash) {
            return Err(sqlx::Error::Protocol(
                format!("Previous hash mismatch at index {}", index).into(),
            ));
        }
    }
    
    Ok(true)
}

What Gets Validated

Block Hash

Recalculated and compared with stored value

Digital Signature

ECDSA signature verified with public key

Chain Linkage

Previous hash reference verified

Merkle Root

Data integrity checked via Merkle tree

Timestamp

Block creation time validated

Validator Auth

Public key is authorized and active

Conflict Resolution

Chain Splits

If nodes receive conflicting blocks:
  1. Validate Both: Check signatures and hashes
  2. Check Height: Accept block from longest valid chain
  3. Timestamp: Use timestamp as tiebreaker if equal height
  4. Propagate: Broadcast the accepted block
// From blockchain/src/lib.rs:318
if chain_height > our_height {
    // Peer has longer chain, sync from them
    self.request_chain_sync(peer_addr, stream).await?;
}

Invalid Blocks

Blocks that fail validation are:
  • Rejected: Not added to chain
  • Logged: Recorded in audit trail
  • Not Propagated: Not broadcast to peers

Audit Trail

Every submission is recorded, even rejections:
Complete History: The audit trail includes all attempted submissions, whether accepted or rejected, providing full transparency.
This creates accountability:
  • Who: Validator public key hash in each block
  • What: All results and changes
  • When: Precise timestamps
  • Why: Validation results

Advantages Over PoW

AspectProof-of-WorkBFT (Ubu-Block)
EnergyHigh (mining)Low (signatures)
SpeedSlow (10+ min)Fast (seconds)
FinalityProbabilisticImmediate
AuthorityAnyoneAuthorized validators
ForksCommonRare
CostExpensive hardwareStandard servers

Security Considerations

Key Management

Critical: Protect private keys at all costs. A compromised key allows an attacker to create fraudulent blocks.
Best practices:
  • Store private.db with 600 permissions
  • Use hardware security modules (HSM) in production
  • Implement key rotation policies
  • Monitor for unauthorized access
  • Regular security audits

Network Security

  • Encrypted Communication: P2P messages encrypted (v0.4)
  • Peer Authentication: Verify node identities
  • Rate Limiting: Prevent spam and DoS
  • Message Validation: Strict message format checking

Validator Compromise

If a validator is compromised:
  1. Revoke Key: Update public key status to revoked
  2. Notify Network: Broadcast revocation
  3. Audit History: Review blocks signed by compromised key
  4. Add New Key: Generate and register new validator

Performance

Block Creation Time

Typical block creation:
  • Hash computation: ~1ms
  • ECDSA signature: ~2ms
  • Merkle tree build: ~5ms (100 results)
  • Total: ~10ms per block

Validation Time

Typical block validation:
  • Hash recalculation: ~1ms
  • Signature verification: ~3ms
  • Merkle verification: ~2ms
  • Total: ~6ms per block
Validation is fast enough to check entire chains with thousands of blocks in seconds.

Future Enhancements

v0.4 - Enhanced Security

  • End-to-end encrypted communication
  • Multi-signature support
  • Threshold signatures

v0.5 - Advanced Consensus

  • Weighted validator votes
  • Reputation system
  • Automatic validator rotation

Blockchain

Understanding the blockchain structure

Node Types

Learn about different node roles

Merkle Trees

How data integrity is maintained

Security

Security best practices

Build docs developers (and LLMs) love