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
| Property | Value |
|---|---|
| Output Size | 256 bits (32 bytes) |
| Block Size | 512 bits (64 bytes) |
| Rounds | 64 |
| Security | 128-bit collision resistance |
| Standard | FIPS 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
| Variant | Output Size | Security | Rate | Capacity |
|---|---|---|---|---|
| SHA3-224 | 224 bits | 112-bit | 1152 bits | 448 bits |
| SHA3-256 | 256 bits | 128-bit | 1088 bits | 512 bits |
| SHA3-384 | 384 bits | 192-bit | 832 bits | 768 bits |
| SHA3-512 | 512 bits | 256-bit | 576 bits | 1024 bits |
| SHAKE128 | Variable | 128-bit | 1344 bits | 256 bits |
| SHAKE256 | Variable | 256-bit | 1088 bits | 512 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
| Property | Value |
|---|---|
| Output Size | 1-64 bytes (configurable) |
| Block Size | 128 bytes |
| Key Size | 0-64 bytes (optional) |
| Salt Size | 16 bytes (optional) |
| Personalization | 16 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 Function | 1KB Input | 1MB Input | 1GB Input | Hardware Accel |
|---|---|---|---|---|
| SHA-256 | 3.2 μs | 3.1 ms | 3.2 s | Yes (ARM crypto) |
| SHA3-256 | 4.8 μs | 4.7 ms | 4.8 s | No |
| BLAKE2b-256 | 1.1 μs | 1.0 ms | 1.1 s | SIMD |
| Keccak-256 | 4.9 μs | 4.8 ms | 4.9 s | No |
Security Considerations
Hash Function Selection
| Use Case | Recommended | Reason |
|---|---|---|
| Block Hashing | SHA-256 | Industry standard, hardware support |
| Transaction IDs | BLAKE2b | Fast, secure |
| Address Generation | Keccak-256 | Ethereum compatibility |
| Password Storage | Argon2id | Memory-hard, side-channel resistant |
| File Integrity | SHA3-256 | NIST standard, quantum-resistant |
| Key Derivation | BLAKE2b | Fast, keyed mode available |
Common Pitfalls
-
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) -
Timing Attacks
// Vulnerable if bytes.Equal(computed, expected) { return true } // Secure - Constant time comparison if subtle.ConstantTimeCompare(computed, expected) == 1 { return true } -
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")
})
}
}