Hash Functions

Cryptographic hash functions - SHA-256, SHA-3, BLAKE2b, and Keccak

Hash Functions

Lux provides a comprehensive suite of cryptographic hash functions for various blockchain operations including block hashing, transaction identification, Merkle trees, and key derivation. All implementations are optimized for performance with hardware acceleration where available.

Overview

Hash functions are fundamental to blockchain security, providing:

  • Data Integrity: Detect any changes to data
  • Unique Identifiers: Generate deterministic IDs for blocks and transactions
  • Proof of Work: Mining and consensus mechanisms
  • Key Derivation: Derive keys from passwords or seed phrases
  • Merkle Trees: Efficient data verification structures

SHA-256

SHA-256 (Secure Hash Algorithm 256-bit) is the workhorse of blockchain technology, used extensively in Bitcoin and many other cryptocurrencies.

Technical Specifications

PropertyValue
Output Size256 bits (32 bytes)
Block Size512 bits (64 bytes)
Rounds64
Security128-bit collision resistance
StandardFIPS 180-4

Implementation

package main

import (
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

// Basic SHA-256 hashing
func hashSHA256(data []byte) [32]byte {
    return sha256.Sum256(data)
}

// Double SHA-256 (used in Bitcoin)
func doubleSHA256(data []byte) [32]byte {
    first := sha256.Sum256(data)
    return sha256.Sum256(first[:])
}

// Streaming SHA-256 for large data
func streamingSHA256(chunks [][]byte) [32]byte {
    h := sha256.New()
    for _, chunk := range chunks {
        h.Write(chunk)
    }
    var result [32]byte
    copy(result[:], h.Sum(nil))
    return result
}

// Merkle root calculation
func merkleRoot(txHashes [][32]byte) [32]byte {
    if len(txHashes) == 0 {
        return [32]byte{}
    }
    if len(txHashes) == 1 {
        return txHashes[0]
    }

    // Build tree level by level
    current := txHashes
    for len(current) > 1 {
        var next [][32]byte
        for i := 0; i < len(current); i += 2 {
            var combined [64]byte
            copy(combined[:32], current[i][:])
            if i+1 < len(current) {
                copy(combined[32:], current[i+1][:])
            } else {
                copy(combined[32:], current[i][:]) // Duplicate last if odd
            }
            next = append(next, sha256.Sum256(combined[:]))
        }
        current = next
    }
    return current[0]
}

Performance Optimization

// Hardware-accelerated SHA-256 (when available)
import (
    "crypto/sha256"
    _ "crypto/sha256/sha256block" // Import for hardware acceleration
)

// Parallel SHA-256 for multiple inputs
func parallelSHA256(inputs [][]byte) [][32]byte {
    results := make([][32]byte, len(inputs))
    var wg sync.WaitGroup

    for i, input := range inputs {
        wg.Add(1)
        go func(idx int, data []byte) {
            defer wg.Done()
            results[idx] = sha256.Sum256(data)
        }(i, input)
    }

    wg.Wait()
    return results
}

// Benchmark comparison
func BenchmarkSHA256(b *testing.B) {
    data := make([]byte, 1024)
    rand.Read(data)

    b.Run("Single", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            _ = sha256.Sum256(data)
        }
    })

    b.Run("Streaming", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            h := sha256.New()
            h.Write(data)
            _ = h.Sum(nil)
        }
    })
}

SHA-3 (Keccak)

SHA-3 provides an alternative to SHA-2 with a completely different internal structure based on the sponge construction.

Technical Specifications

VariantOutput SizeSecurityRateCapacity
SHA3-224224 bits112-bit1152 bits448 bits
SHA3-256256 bits128-bit1088 bits512 bits
SHA3-384384 bits192-bit832 bits768 bits
SHA3-512512 bits256-bit576 bits1024 bits
SHAKE128Variable128-bit1344 bits256 bits
SHAKE256Variable256-bit1088 bits512 bits

Implementation

import (
    "golang.org/x/crypto/sha3"
)

// SHA3-256 hashing
func hashSHA3_256(data []byte) [32]byte {
    return sha3.Sum256(data)
}

// Keccak-256 (Ethereum compatible)
func hashKeccak256(data []byte) [32]byte {
    h := sha3.NewLegacyKeccak256()
    h.Write(data)
    var result [32]byte
    copy(result[:], h.Sum(nil))
    return result
}

// SHAKE256 - Extensible output function
func shake256(data []byte, outputLen int) []byte {
    h := sha3.NewShake256()
    h.Write(data)
    output := make([]byte, outputLen)
    h.Read(output)
    return output
}

// Domain separation with SHA3
func domainSeparatedHash(domain string, data []byte) [32]byte {
    h := sha3.New256()
    h.Write([]byte(domain))
    h.Write([]byte{0x00}) // Null separator
    h.Write(data)
    var result [32]byte
    copy(result[:], h.Sum(nil))
    return result
}

Ethereum Address Generation

// Generate Ethereum address from public key
func ethereumAddress(publicKey []byte) [20]byte {
    // Remove the 0x04 prefix if present
    if len(publicKey) == 65 && publicKey[0] == 0x04 {
        publicKey = publicKey[1:]
    }

    // Keccak-256 hash of public key
    hash := hashKeccak256(publicKey)

    // Take last 20 bytes
    var address [20]byte
    copy(address[:], hash[12:])
    return address
}

// EIP-55 checksum address encoding
func checksumAddress(address [20]byte) string {
    // Convert to hex without 0x prefix
    hex := hex.EncodeToString(address[:])

    // Hash the lowercase hex
    hash := hashKeccak256([]byte(hex))

    // Apply checksum
    var result []byte
    for i, char := range hex {
        hashByte := hash[i/2]
        if i%2 == 0 {
            hashByte = hashByte >> 4
        } else {
            hashByte = hashByte & 0x0f
        }

        if hashByte >= 8 && char >= 'a' && char <= 'f' {
            result = append(result, byte(char-32)) // Uppercase
        } else {
            result = append(result, byte(char))
        }
    }

    return "0x" + string(result)
}

BLAKE2b

BLAKE2b is a high-speed cryptographic hash function, faster than SHA-256 while maintaining equivalent security.

Technical Specifications

PropertyValue
Output Size1-64 bytes (configurable)
Block Size128 bytes
Key Size0-64 bytes (optional)
Salt Size16 bytes (optional)
Personalization16 bytes (optional)
Performance~3x faster than SHA-256

Implementation

import (
    "github.com/luxfi/crypto/blake2b"
    "golang.org/x/crypto/blake2b"
)

// Basic BLAKE2b-256
func hashBLAKE2b256(data []byte) [32]byte {
    return blake2b.Sum256(data)
}

// BLAKE2b with custom output size
func hashBLAKE2bCustom(data []byte, size int) []byte {
    h, err := blake2b.New(size, nil)
    if err != nil {
        panic(err)
    }
    h.Write(data)
    return h.Sum(nil)
}

// Keyed BLAKE2b (MAC)
func blake2bMAC(key, message []byte) [32]byte {
    h, err := blake2b.New256(key)
    if err != nil {
        panic(err)
    }
    h.Write(message)
    var result [32]byte
    copy(result[:], h.Sum(nil))
    return result
}

// BLAKE2b with salt and personalization
func blake2bAdvanced(data, salt, personal []byte) [64]byte {
    config := &blake2b.Config{
        Size:     64,
        Salt:     salt,
        Personal: personal,
    }

    h, err := blake2b.New(config)
    if err != nil {
        panic(err)
    }

    h.Write(data)
    var result [64]byte
    copy(result[:], h.Sum(nil))
    return result
}

// Tree hashing with BLAKE2b
type BLAKE2bTree struct {
    fanout      uint8
    maxDepth    uint8
    leafSize    uint32
    nodeOffset  uint64
    nodeDepth   uint8
    innerLength uint8
    isLastNode  bool
}

func (t *BLAKE2bTree) Hash(data [][]byte) [32]byte {
    // Implementation of tree mode
    // Allows parallel hashing of large files
    leaves := make([][32]byte, len(data))

    // Hash leaves in parallel
    var wg sync.WaitGroup
    for i, chunk := range data {
        wg.Add(1)
        go func(idx int, d []byte) {
            defer wg.Done()
            leaves[idx] = blake2b.Sum256(d)
        }(i, chunk)
    }
    wg.Wait()

    // Build tree upwards
    for len(leaves) > 1 {
        var parents [][32]byte
        for i := 0; i < len(leaves); i += 2 {
            if i+1 < len(leaves) {
                combined := append(leaves[i][:], leaves[i+1][:]...)
                parents = append(parents, blake2b.Sum256(combined))
            } else {
                parents = append(parents, leaves[i])
            }
        }
        leaves = parents
    }

    return leaves[0]
}

Performance Comparison

func BenchmarkHashFunctions(b *testing.B) {
    data := make([]byte, 1024)
    rand.Read(data)

    b.Run("SHA256", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            _ = sha256.Sum256(data)
        }
    })

    b.Run("SHA3-256", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            _ = sha3.Sum256(data)
        }
    })

    b.Run("BLAKE2b-256", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            _ = blake2b.Sum256(data)
        }
    })

    b.Run("Keccak-256", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            h := sha3.NewLegacyKeccak256()
            h.Write(data)
            _ = h.Sum(nil)
        }
    })
}

Hash-Based Applications

Merkle Trees

Efficient data structure for blockchain verification:

type MerkleTree struct {
    root   [32]byte
    leaves [][32]byte
    nodes  [][][32]byte
}

func NewMerkleTree(data [][]byte) *MerkleTree {
    mt := &MerkleTree{
        leaves: make([][32]byte, len(data)),
    }

    // Hash all leaves
    for i, d := range data {
        mt.leaves[i] = sha256.Sum256(d)
    }

    // Build tree
    mt.buildTree()
    return mt
}

func (mt *MerkleTree) buildTree() {
    current := mt.leaves
    mt.nodes = append(mt.nodes, current)

    for len(current) > 1 {
        var next [][32]byte
        for i := 0; i < len(current); i += 2 {
            left := current[i]
            right := left
            if i+1 < len(current) {
                right = current[i+1]
            }

            combined := append(left[:], right[:]...)
            next = append(next, sha256.Sum256(combined))
        }

        mt.nodes = append(mt.nodes, next)
        current = next
    }

    if len(current) > 0 {
        mt.root = current[0]
    }
}

func (mt *MerkleTree) GetProof(index int) [][32]byte {
    var proof [][32]byte

    for level := 0; level < len(mt.nodes)-1; level++ {
        levelNodes := mt.nodes[level]
        if index^1 < len(levelNodes) {
            proof = append(proof, levelNodes[index^1])
        }
        index /= 2
    }

    return proof
}

func VerifyMerkleProof(leaf, root [32]byte, proof [][32]byte, index int) bool {
    current := leaf

    for _, sibling := range proof {
        var combined [64]byte
        if index&1 == 0 {
            copy(combined[:32], current[:])
            copy(combined[32:], sibling[:])
        } else {
            copy(combined[:32], sibling[:])
            copy(combined[32:], current[:])
        }
        current = sha256.Sum256(combined[:])
        index /= 2
    }

    return current == root
}

Password Hashing (KDF)

Key derivation functions for secure password storage:

import (
    "golang.org/x/crypto/argon2"
    "golang.org/x/crypto/scrypt"
)

// Argon2id - recommended for password hashing
func hashPassword(password string, salt []byte) []byte {
    // Argon2id parameters
    time := uint32(1)        // Iterations
    memory := uint32(64 * 1024) // 64 MB
    threads := uint8(4)      // Parallelism
    keyLen := uint32(32)     // Output length

    return argon2.IDKey([]byte(password), salt, time, memory, threads, keyLen)
}

// Scrypt - alternative KDF
func scryptHash(password string, salt []byte) []byte {
    dk, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
    if err != nil {
        panic(err)
    }
    return dk
}

// PBKDF2 - legacy support
import "golang.org/x/crypto/pbkdf2"

func pbkdf2Hash(password string, salt []byte) []byte {
    return pbkdf2.Key([]byte(password), salt, 100000, 32, sha256.New)
}

Bloom Filters

Probabilistic data structure using hash functions:

type BloomFilter struct {
    bits     []uint64
    size     uint64
    hashFunc []func([]byte) uint64
}

func NewBloomFilter(size uint64, hashCount int) *BloomFilter {
    bf := &BloomFilter{
        bits:     make([]uint64, (size+63)/64),
        size:     size,
        hashFunc: make([]func([]byte) uint64, hashCount),
    }

    // Create hash functions using different seeds
    for i := 0; i < hashCount; i++ {
        seed := uint64(i)
        bf.hashFunc[i] = func(data []byte) uint64 {
            h := sha256.Sum256(append(data, byte(seed)))
            return binary.BigEndian.Uint64(h[:8]) % size
        }
    }

    return bf
}

func (bf *BloomFilter) Add(data []byte) {
    for _, hash := range bf.hashFunc {
        pos := hash(data)
        word := pos / 64
        bit := pos % 64
        bf.bits[word] |= 1 << bit
    }
}

func (bf *BloomFilter) Contains(data []byte) bool {
    for _, hash := range bf.hashFunc {
        pos := hash(data)
        word := pos / 64
        bit := pos % 64
        if bf.bits[word]&(1<<bit) == 0 {
            return false
        }
    }
    return true
}

Performance Benchmarks

Performance characteristics on Apple M1 processor:

Hash Function1KB Input1MB Input1GB InputHardware Accel
SHA-2563.2 μs3.1 ms3.2 sYes (ARM crypto)
SHA3-2564.8 μs4.7 ms4.8 sNo
BLAKE2b-2561.1 μs1.0 ms1.1 sSIMD
Keccak-2564.9 μs4.8 ms4.9 sNo

Security Considerations

Hash Function Selection

Use CaseRecommendedReason
Block HashingSHA-256Industry standard, hardware support
Transaction IDsBLAKE2bFast, secure
Address GenerationKeccak-256Ethereum compatibility
Password StorageArgon2idMemory-hard, side-channel resistant
File IntegritySHA3-256NIST standard, quantum-resistant
Key DerivationBLAKE2bFast, keyed mode available

Common Pitfalls

  1. Length Extension Attacks

    // Vulnerable - SHA-256 is susceptible
    mac := sha256.Sum256(append(secret, message...))
    
    // Secure - Use HMAC
    h := hmac.New(sha256.New, secret)
    h.Write(message)
    mac := h.Sum(nil)
  2. Timing Attacks

    // Vulnerable
    if bytes.Equal(computed, expected) {
        return true
    }
    
    // Secure - Constant time comparison
    if subtle.ConstantTimeCompare(computed, expected) == 1 {
        return true
    }
  3. Rainbow Table Attacks

    // Vulnerable - No salt
    hash := sha256.Sum256([]byte(password))
    
    // Secure - With salt
    salt := make([]byte, 16)
    rand.Read(salt)
    hash := argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32)

Testing

Comprehensive test suite for hash functions:

func TestHashFunctions(t *testing.T) {
    testVectors := []struct {
        name     string
        input    string
        sha256   string
        sha3_256 string
        blake2b  string
        keccak   string
    }{
        {
            name:     "empty",
            input:    "",
            sha256:   "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
            sha3_256: "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a",
            blake2b:  "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8",
            keccak:   "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
        },
        {
            name:     "abc",
            input:    "abc",
            sha256:   "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
            sha3_256: "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532",
            blake2b:  "bddd813c634239723171ef3fee98579b94964e3bb1cb3e427262c8c068d52319",
            keccak:   "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45",
        },
    }

    for _, tv := range testVectors {
        t.Run(tv.name, func(t *testing.T) {
            input := []byte(tv.input)

            // Test SHA-256
            sha256Hash := fmt.Sprintf("%x", sha256.Sum256(input))
            require.Equal(t, tv.sha256, sha256Hash, "SHA-256 mismatch")

            // Test SHA3-256
            sha3Hash := fmt.Sprintf("%x", sha3.Sum256(input))
            require.Equal(t, tv.sha3_256, sha3Hash, "SHA3-256 mismatch")

            // Test BLAKE2b-256
            blake2bHash := fmt.Sprintf("%x", blake2b.Sum256(input))
            require.Equal(t, tv.blake2b, blake2bHash, "BLAKE2b mismatch")

            // Test Keccak-256
            h := sha3.NewLegacyKeccak256()
            h.Write(input)
            keccakHash := fmt.Sprintf("%x", h.Sum(nil))
            require.Equal(t, tv.keccak, keccakHash, "Keccak-256 mismatch")
        })
    }
}

References