Cryptographic Hashing

Cryptographic hash functions are fundamental building blocks of blockchain technology, providing data integrity, unique identification, and security guarantees.

What Is a Hash Function?

A hash function takes input of any size and produces a fixed-size output called a digest or hash.

Text
┌─────────────────────────────────────────────────────────────────┐
│                    Hash Function Properties                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Input (any size)              Output (fixed size)              │
│  ┌───────────────┐            ┌───────────────────┐            │
│  │ "Hello"       │ ─────────▶ │ 2cf24dba5fb0a30.. │            │
│  │ (5 bytes)SHA-256(32 bytes/256 bit)│            │
│  └───────────────┘            └───────────────────┘            │
│                                                                 │
│  ┌───────────────┐            ┌───────────────────┐            │
│  │ War and Peace │ ─────────▶ │ 7e81eb3d99e0b0d.. │            │
│  │ (3.2 MB)SHA-256(32 bytes/256 bit)│            │
│  └───────────────┘            └───────────────────┘            │
│                                                                 │
│  Key Properties:                                                │
│  • Deterministic: Same input → same output                     │
│  • Fast: Computes quickly                                      │
│  • One-way: Cannot reverse to find input                       │
│  • Collision-resistant: Hard to find two inputs with same hash │
│  • Avalanche: Small input change → drastically different hash  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Hash Functions in Practice

TypeScript
import { createHash } from "crypto";

// Basic hashing
function sha256(data: string | Buffer): string {
  return createHash("sha256").update(data).digest("hex");
}

// Examples
console.log(sha256("Hello"));
// 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

console.log(sha256("Hello!")); // One character difference
// 334d016f755cd6dc58c53a86e183882f8ec14f52fb05345887c8a5edd42c87b7

console.log(sha256("Hello")); // Same input = same output
// 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

// Hashing files
import { readFileSync } from "fs";

function hashFile(path: string): string {
  const content = readFileSync(path);
  return sha256(content);
}

// Hashing objects
function hashObject(obj: object): string {
  // Sort keys for deterministic serialization
  const sorted = JSON.stringify(obj, Object.keys(obj).sort());
  return sha256(sorted);
}

const tx1 = { from: "Alice", to: "Bob", amount: 100 };
const tx2 = { to: "Bob", from: "Alice", amount: 100 };

console.log(hashObject(tx1)); // Same hash despite different key order
console.log(hashObject(tx2)); // Same hash

Common Hash Algorithms

AlgorithmOutput SizeSecurityUse Case
MD5128 bitsBrokenLegacy checksums only
SHA-1160 bitsWeakLegacy, avoid
SHA-256256 bitsStrongBitcoin, general use
SHA-512512 bitsStrongHigh security needs
Keccak-256256 bitsStrongEthereum
BLAKE3VariableStrongHigh performance

Blockchain Applications

Transaction Hashing

TypeScript
interface Transaction {
  sender: string;
  recipient: string;
  amount: number;
  nonce: number;
  timestamp: number;
}

function hashTransaction(tx: Transaction): string {
  const data = Buffer.concat([
    Buffer.from(tx.sender),
    Buffer.from(tx.recipient),
    Buffer.from(tx.amount.toString()),
    Buffer.from(tx.nonce.toString()),
    Buffer.from(tx.timestamp.toString()),
  ]);
  return sha256(data);
}

// Transaction ID is its hash
const tx: Transaction = {
  sender: "Alice",
  recipient: "Bob",
  amount: 100,
  nonce: 1,
  timestamp: Date.now(),
};

const txId = hashTransaction(tx);
console.log(`Transaction ID: ${txId}`);

Block Hashing

TypeScript
interface Block {
  index: number;
  previousHash: string;
  timestamp: number;
  transactions: Transaction[];
  nonce: number;
}

function hashBlock(block: Block): string {
  const txHashes = block.transactions.map(hashTransaction).join("");

  const data = Buffer.concat([
    Buffer.from(block.index.toString()),
    Buffer.from(block.previousHash),
    Buffer.from(block.timestamp.toString()),
    Buffer.from(txHashes),
    Buffer.from(block.nonce.toString()),
  ]);

  return sha256(data);
}

// Chain integrity check
function verifyChain(blocks: Block[]): boolean {
  for (let i = 1; i < blocks.length; i++) {
    const currentBlock = blocks[i];
    const previousBlock = blocks[i - 1];

    // Verify previous hash reference
    if (currentBlock.previousHash !== hashBlock(previousBlock)) {
      return false;
    }

    // Verify current block hash (for PoW chains)
    const currentHash = hashBlock(currentBlock);
    if (!currentHash.startsWith("0000")) {
      return false;
    }
  }
  return true;
}

Merkle Root

TypeScript
function computeMerkleRoot(hashes: string[]): string {
  if (hashes.length === 0) return sha256("");
  if (hashes.length === 1) return hashes[0];

  const nextLevel: string[] = [];

  for (let i = 0; i < hashes.length; i += 2) {
    const left = hashes[i];
    const right = hashes[i + 1] || left; // Duplicate if odd
    nextLevel.push(sha256(left + right));
  }

  return computeMerkleRoot(nextLevel);
}

// Example: 4 transactions
const txHashes = ["a1b2c3...", "d4e5f6...", "g7h8i9...", "j0k1l2..."];

const merkleRoot = computeMerkleRoot(txHashes);
// This single hash represents all transactions

Address Derivation

Blockchain addresses are typically derived from public keys using hashing:

TypeScript
import { createHash } from "crypto";
import bs58 from "bs58";

// Bitcoin-style address (simplified)
function bitcoinAddress(publicKey: Buffer): string {
  // SHA-256 then RIPEMD-160
  const sha256Hash = createHash("sha256").update(publicKey).digest();
  const ripemd160Hash = createHash("ripemd160").update(sha256Hash).digest();

  // Add version byte and checksum
  const versioned = Buffer.concat([Buffer.from([0x00]), ripemd160Hash]);
  const checksum = createHash("sha256")
    .update(createHash("sha256").update(versioned).digest())
    .digest()
    .slice(0, 4);

  return bs58.encode(Buffer.concat([versioned, checksum]));
}

// Ethereum-style address
function ethereumAddress(publicKey: Buffer): string {
  // Remove the 0x04 prefix if present
  const key = publicKey.length === 65 ? publicKey.slice(1) : publicKey;

  // Keccak-256 hash, take last 20 bytes
  const hash = keccak256(key);
  return "0x" + hash.slice(-40);
}

// Solana uses the public key directly (base58 encoded)
function solanaAddress(publicKey: Buffer): string {
  return bs58.encode(publicKey);
}

Security Properties

Preimage Resistance

Given a hash h, it's computationally infeasible to find any message m such that hash(m) = h.

TypeScript
// This is infeasible:
function findPreimage(targetHash: string): string {
  // Would need to try ~2^256 possibilities for SHA-256
  // That's more atoms than exist in the observable universe
  let attempt = 0;
  while (true) {
    const guess = attempt.toString();
    if (sha256(guess) === targetHash) {
      return guess;
    }
    attempt++;
  }
}

Collision Resistance

It's computationally infeasible to find two different messages that produce the same hash.

TypeScript
// This is also infeasible for secure hash functions:
function findCollision(): [string, string] {
  const seen = new Map<string, string>();
  let attempt = 0;

  while (true) {
    const message = `message_${attempt}`;
    const hash = sha256(message);

    if (seen.has(hash)) {
      return [seen.get(hash)!, message];
    }
    seen.set(hash, message);
    attempt++;
  }
  // Would need ~2^128 attempts (birthday paradox)
}

Proof of Work

Hash functions enable proof of work through difficulty targeting:

TypeScript
interface ProofOfWork {
  nonce: number;
  hash: string;
}

function mineBlock(data: string, difficulty: number): ProofOfWork {
  const target = "0".repeat(difficulty);
  let nonce = 0;

  while (true) {
    const hash = sha256(data + nonce.toString());

    if (hash.startsWith(target)) {
      return { nonce, hash };
    }
    nonce++;
  }
}

// Example: find hash starting with "0000"
const result = mineBlock("Hello, Blockchain!", 4);
console.log(`Nonce: ${result.nonce}`);
console.log(`Hash: ${result.hash}`);
// Hash will start with 0000...

Best Practices

PracticeDescription
Use modern algorithmsSHA-256, SHA-3, BLAKE3
Never use MD5/SHA-1Cryptographically broken
Salt sensitive dataAdd random salt before hashing passwords
Use constant-time comparisonPrevent timing attacks
Verify integrityAlways verify hashes match expectations

Next: Public/Private Keys - Asymmetric cryptography and key pairs.