Skip to main content

Overview

Ubu-Block implements a transparent, tamper-evident blockchain designed specifically for election results. The blockchain stores polling station results in an immutable, distributed ledger that anyone can verify.

Core Features

Immutable

You can’t make changes, only append—no tampering allowed

Distributed

No single point of data storage across multiple nodes

Accessible

Runs on SQLite, widely supported and SQL-friendly

Auditable

All submissions (accepted or rejected) are recorded

Block Structure

Each block in the Ubu-Block blockchain contains the following components:
pub struct Block {
    pub hash: String,                      // SHA3-256 hash of the block header
    pub hash_signature: String,            // Digital signature of the hash
    pub merkle_root: [u8; 32],            // Root of Merkle tree for results
    pub inner: BlockType,                  // Block content (Genesis or Results)
    pub height: usize,                     // Position in the chain
    pub signature_pub_key_hash: String,   // Hash of validator's public key
    pub timestamp: DateTime<Utc>,         // When the block was created
    pub prev_hash: String,                // Hash of previous block
    pub prev_hash_signature: String,      // Signature of previous hash
    pub creator: String,                   // Node that created the block
    pub creator_pub_key: String,          // Creator's public key hash
    pub version: usize,                    // Protocol version
}

Block Header

The block header is used to generate the block hash and contains:
pub struct ElectionBlockHeader {
    pub previous_hash: [u8; 32],      // Links to previous block
    pub merkle_root: [u8; 32],        // Hash of all election results
    pub timestamp: i64,                // Unix timestamp
    pub block_number: i64,             // Height in chain
    pub validator_signature: String,   // Instead of PoW nonce
}
Ubu-Block uses validator signatures instead of Proof-of-Work (PoW), making it more efficient for election data where authorized nodes validate submissions.

Block Types

There are two types of blocks:

Genesis Block

The first block in the chain that initializes the blockchain:
  • Height: 0
  • Previous hash: All zeros (0x0000...0000)
  • Contains initial setup data (constituencies, parties, stations)
  • Created during blockchain initialization

Result Block

Regular blocks containing election results:
pub enum BlockType {
    Genesis,
    Result(Vec<CandidateResult>),
}

pub struct CandidateResult {
    pub station_id: i64,     // Polling station identifier
    pub candidate_id: i64,   // Candidate identifier
    pub votes: i64,          // Number of votes
}

How Blocks Are Created

  1. Collect Results: Submission nodes gather polling station results
  2. Build Merkle Tree: Create a Merkle tree from all results in the block
  3. Create Header: Construct the block header with previous hash and Merkle root
  4. Hash Header: Generate SHA3-256 hash of the header
  5. Sign Block: Authorized node signs the hash with their private key
  6. Broadcast: New block is announced to all peers via P2P network
// Example: Creating a new block
let block = Block::new(
    &signer,           // (private_key, public_key, metadata)
    &prev_hash,        // Hash of previous block
    results,           // Vec<CandidateResult>
    height,            // Current chain height + 1
    merkle_root,       // Root of Merkle tree
);

Chain Validation

The blockchain validates integrity through multiple checks:

1. Hash Verification

Recalculate the block hash and compare with stored value:
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,
};

let calculated_hash = hash_block(&header);
assert_eq!(calculated_hash, block.hash);

2. Signature Verification

Verify the digital signature using ECDSA (P-256):
let verifier: VerifyingKey = deserialize(&pub_key.bytes).unwrap();
let signature: Signature = deserialize(&signature_bytes).unwrap();
verifier.verify(calculated_hash.as_bytes(), &signature).unwrap();

3. Chain Linkage

Ensure each block properly references its predecessor:
assert_eq!(block.prev_hash, hex::encode(prev_hash));

4. Merkle Root Validation

Verify the Merkle root matches the results (see Merkle Trees).

Tamper Detection

Trying to modify data triggers validation failures:
UPDATE "results" SET "votes" = 71 WHERE _rowid_ = 1;
The blockchain detects tampering because modifying any data changes the Merkle root, which invalidates the block hash and its signature.

Storage Layer

Ubu-Block uses SQLite for storage, making it:
  • Portable: Single file database
  • SQL-friendly: Query with standard SQL
  • Lightweight: No heavy database server required
  • Transparent: Anyone can inspect the database directly

Database Structure

Two separate databases:
  1. blockchain.db (Public):
    • blockchain table: Block headers and metadata
    • results table: Election results
    • pubkeys table: Validator public keys
    • Geographic data (counties, constituencies, wards, stations)
    • Candidate and party information
  2. private.db (Private):
    • privkeys table: Node’s private keys for signing

Node Types

Learn about Submission, Observer, and Verification nodes

Consensus

Understand the BFT consensus mechanism

Merkle Trees

How data integrity is maintained

Quick Start

Initialize your first blockchain

Build docs developers (and LLMs) love