Skip to main content
Ubu-Block uses industry-standard cryptographic primitives to ensure the integrity and authenticity of election data. This page details the cryptographic implementation.

Cryptographic Algorithms

ECDSA (Elliptic Curve Digital Signature Algorithm)

Ubu-Block uses P-256 (also known as secp256r1 or prime256v1) for digital signatures. This is the same curve used by Bitcoin and many other blockchain systems.
use p256::ecdsa::{Signature, SigningKey, VerifyingKey, signature::Signer};
use p256::elliptic_curve::rand_core::OsRng;
Key Generation:
pub fn get_private_key() -> SigningKey {
    SigningKey::random(&mut OsRng)
}

pub fn get_public_key(key: &SigningKey) -> VerifyingKey {
    *key.verifying_key()
}
The system generates cryptographically secure random keys using the operating system’s random number generator (OsRng).
P-256 provides 128-bit security strength and is approved by NIST (National Institute of Standards and Technology).

SHA3-256 Hashing

Ubu-Block uses SHA3-256 (Keccak) for all hashing operations, including:
  • Block header hashing
  • Public key hashing
  • Merkle tree node hashing
use sha3::{Digest, Sha3_256 as Sha256};

pub fn sha256_digest<T: Serialize>(data: &T) -> String {
    let block_bytes = serialize(data).unwrap();
    let mut hasher = Sha256::new();
    hasher.update(block_bytes);
    format!("{:x}", hasher.finalize())
}
SHA3-256 is different from SHA-256. SHA3 is the latest member of the Secure Hash Algorithm family and provides better security properties against length extension attacks.

Signature Process

Signing Block Hashes

Each block is signed by its creator using their private key:
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())
}
What gets signed:
  1. Block hash - The SHA3-256 hash of the block header
  2. Previous block hash - Links the current block to the previous one
Both signatures are stored in the blockchain:
  • hash_signature - Signature of the current block’s hash
  • prev_hash_signature - Signature of the previous block’s hash

Verification Process

The verification process is implemented in the database layer:
// Get the public key
let pub_key = self.get_public_key(&block.signature_pub_key_hash).await?;
let verifier: VerifyingKey = deserialize(&pub_key.bytes).unwrap();

// Decode the signature
let signature_bytes = hex::decode(&block.hash_signature).unwrap();
let signature: Signature = deserialize(&signature_bytes).unwrap();

// Verify the signature
verifier.verify(calculated_hash.as_bytes(), &signature).unwrap();
See crates/database/src/lib.rs:272-279 for the full implementation.

Block Header Structure

The block header contains all critical information that gets hashed:
pub struct ElectionBlockHeader {
    pub previous_hash: [u8; 32],
    pub merkle_root: [u8; 32],
    pub timestamp: i64,
    pub block_number: i64,
    pub validator_signature: String,
}
The header is serialized using bincode before hashing to ensure deterministic byte representation.

Merkle Tree Implementation

Ubu-Block uses Merkle trees to efficiently verify election results. Each leaf node contains a hash of a single election result:
fn hash_election_result(result: &CandidateResult) -> [u8; 32] {
    let serialized = bincode::serialize(result).unwrap();
    let mut hasher = Sha256::new();
    hasher.update(&serialized);
    hasher.finalize().into()
}
Parent nodes combine their children’s hashes:
fn hash_pair(left: [u8; 32], right: [u8; 32]) -> [u8; 32] {
    let mut hasher = Sha256::new();
    hasher.update(left);
    hasher.update(right);
    hasher.finalize().into()
}
The Merkle root is stored in the block header, allowing efficient verification of individual results without needing the entire block.

Public Key Hashing

Public keys are hashed to create shorter identifiers:
let sigkey_hash = sha256_digest(&signer.1);
This hash is stored as signature_pub_key_hash in the blockchain and used to look up the full public key when needed for verification.
Never share your private key. The system only stores private keys in the private_db database, which should be kept secure and backed up.

Security Properties

Collision Resistance

SHA3-256 makes it computationally infeasible to find two different inputs that produce the same hash output.

Signature Unforgeability

P-256 ECDSA ensures that only the holder of the private key can create valid signatures. Without the private key, it’s computationally infeasible to forge a signature.

Immutability

Once a block is added and signed, any modification to its contents will:
  1. Change the block hash
  2. Invalidate the signature
  3. Break the chain linkage to subsequent blocks

Deterministic Verification

All verification operations are deterministic - given the same inputs, they always produce the same result, enabling independent verification by any node.

Build docs developers (and LLMs) love