Skip to content

ch4r10t33r/hash-zig

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

hash-zig

CI Zig License

A pure Zig implementation of hash-based signatures using Poseidon2 and SHA3 with incomparable encodings. This library implements Generalized XMSS signatures based on the framework from this paper, with exact compatibility with the hash-sig Rust implementation. Features include PRF-based key derivation, epoch management, encoding randomness, and full Merkle tree storage. Poseidon2 targets the KoalaBear 31‑bit field with Montgomery arithmetic (compatible with plonky3 constants).

🌟 Features

Rust Compatibility βœ…

  • Exact Rust Implementation Match: Key structures, signatures, and API match hash-sig Rust implementation
  • PRF-Based Key Derivation: Derives OTS keys on-demand from a 32-byte PRF key (not storing all keys)
  • Epoch Management: Supports activation_epoch and num_active_epochs for flexible key lifetimes
  • Encoding Randomness: Includes rho (encoding randomness) in signatures for security
  • Full Tree Storage: Stores complete Merkle tree structure (all 2047 nodes for 1024 leaves) in secret key
  • Self-Contained Keys: Parameters stored in public and secret keys

Hash Functions & Parameters

  • Poseidon2 (KoalaBear): Width=16, external_rounds=8, internal_rounds=20, sbox_degree=3, Montgomery arithmetic
  • SHA3-256: NIST-standardized cryptographic hash
  • Hypercube Parameters: 64 chains of length 8 (w=3) from hypercube-hashsig-parameters
  • Binary Encoding: Incomparable binary encoding with randomness

Implementation Quality

  • 128-bit Post-Quantum Security: Well-tested security level
  • Flexible Lifetimes: 2^10 to 2^32 signatures per keypair
  • Parallel Key Generation: Multi-threaded with atomic work queues (~3x speedup on 8-core M2)
  • Pure Zig: Minimal dependencies, fully type-safe
  • Comprehensive Tests: Unit and integration tests

πŸ“‹ Table of Contents

πŸš€ Installation

Using Zig Package Manager

Add to your build.zig.zon:

.{
    .name = .my_project,
    .version = "0.1.0",
    .dependencies = .{
        .@"hash-zig" = .{
            .url = "https://github.com/ch4r10t33r/hash-zig/archive/refs/tags/v0.1.0.tar.gz",
            .hash = "1220...", // Will be generated by zig build
        },
    },
}

In your build.zig:

const hash_zig_dep = b.dependency("hash-zig", .{
    .target = target,
    .optimize = optimize,
});

exe.root_module.addImport("hash-zig", hash_zig_dep.module("hash-zig"));

Manual Installation

git clone https://github.com/ch4r10t33r/hash-zig.git
cd hash-zig
zig build test

⚑ Quick Start

const std = @import("std");
const hash_zig = @import("hash-zig");

pub fn main() !void {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    // Initialize with medium lifetime (2^16 signatures)
    // Only 128-bit security is supported
    const params = hash_zig.Parameters.initHypercube(.lifetime_2_16);
    var sig_scheme = try hash_zig.HashSignature.init(allocator, params);
    defer sig_scheme.deinit();

    // Generate a random seed for key generation (32 bytes required)
    var seed: [32]u8 = undefined;
    std.crypto.random.bytes(&seed);

    // Generate keypair from seed with epoch management
    // Parameters: seed, activation_epoch, num_active_epochs (0 = use full lifetime)
    var keypair = try sig_scheme.generateKeyPair(allocator, &seed, 0, 0);
    defer keypair.deinit(allocator);

    // Sign a message (epoch must be tracked by your application!)
    const message = "Hello, hash-based signatures!";
    const epoch: u64 = 0; // YOUR APP must track this and never reuse!
    
    // RNG seed for encoding randomness (rho)
    var rng_seed: [32]u8 = undefined;
    std.crypto.random.bytes(&rng_seed);
    
    var signature = try sig_scheme.sign(allocator, message, &keypair.secret_key, epoch, &rng_seed);
defer signature.deinit(allocator);

    // Verify signature
    const is_valid = try sig_scheme.verify(allocator, message, signature, &keypair.public_key);
    std.debug.print("Signature valid: {}\n", .{is_valid});
}

πŸ› οΈ Programs

The hash-zig library includes several built-in programs for demonstration, testing, and performance analysis:

Basic Example (hash-zig-example)

Purpose: Demonstrates basic usage of the hash-zig library Command: zig build example or zig build run Description: Shows how to generate keypairs, sign messages, and verify signatures using the Rust-compatible implementation. Includes timing measurements, displays key information (PRF key, tree structure, epoch management), and demonstrates encoding randomness. Perfect for understanding the library's core functionality.

Performance Benchmark (hash-zig-benchmark)

Purpose: Comprehensive performance benchmarking Command: zig build benchmark Description: Runs standardized performance tests across different key lifetimes (2^10 and 2^16). Measures key generation (with on-demand key derivation from PRF), signing (with encoding randomness), and verification times with detailed metrics. Outputs results in CI-friendly format for automated testing. Uses the Rust-compatible implementation.

SIMD Benchmark (hash-zig-simd-benchmark)

Purpose: Tests SIMD-optimized implementations Command: zig build simd-benchmark Description: Benchmarks SIMD-optimized versions of the hash-based signature scheme. Tests both 2^10 and 2^16 lifetimes with SIMD acceleration. Useful for measuring performance with vectorization. Uses the same Rust-compatible architecture as the standard implementation but with SIMD optimizations.

Implementation Comparison (hash-zig-compare)

Purpose: Compare Standard vs SIMD implementations Command: zig build compare Description: Compares the performance and correctness of the standard (scalar) vs SIMD implementations. Both use identical Rust-compatible architecture (PRF-based key derivation, epoch management, encoding randomness) and hypercube parameters (64 chains Γ— 8 length). Shows performance speedups from SIMD optimizations and verifies public key consistency.

Building All Programs

# Build all executables
zig build

# Run specific programs
zig build example         # Basic usage demo (Rust-compatible)
zig build benchmark       # Standard benchmark
zig build simd-benchmark  # SIMD benchmark
zig build compare         # Compare Standard vs SIMD

Program Outputs

All programs provide detailed timing information and can be used for:

  • Development: Understanding library behavior and performance characteristics
  • Testing: Verifying correct implementation and performance expectations
  • Benchmarking: Comparing different implementations and optimizations
  • CI/CD: Automated performance regression testing

πŸ“– Usage

Basic Signing and Verification

const hash_zig = @import("hash-zig");

// Configure parameters with Poseidon2 (default)
// 128-bit security with hypercube parameters: 64 chains of length 8 (w=3)
const params = hash_zig.Parameters.initHypercube(.lifetime_2_16);
var sig = try hash_zig.HashSignature.init(allocator, params);
defer sig.deinit();

// Generate a random seed (32 bytes required)
var seed: [32]u8 = undefined;
std.crypto.random.bytes(&seed);

// Generate keys from seed with epoch management
// activation_epoch: 0 (start from beginning)
// num_active_epochs: 0 (use full lifetime)
var keypair = try sig.generateKeyPair(allocator, &seed, 0, 0);
defer keypair.deinit(allocator);

// Sign (YOU must track epochs and never reuse!)
const epoch: u64 = 0; // Track this in your app's database!

// Generate RNG seed for encoding randomness (rho)
var rng_seed: [32]u8 = undefined;
std.crypto.random.bytes(&rng_seed);

var signature = try sig.sign(allocator, "message", &keypair.secret_key, epoch, &rng_seed);
defer signature.deinit(allocator);

// Verify
const valid = try sig.verify(allocator, "message", signature, &keypair.public_key);

Deterministic Key Generation

The generateKeyPair function requires a 32-byte seed. You can use this for:

  • Random key generation: Use std.crypto.random.bytes(&seed)
  • Deterministic key generation: Derive seed from a password/phrase using a KDF
  • Key recovery: Store the seed securely to regenerate the same keypair
// Random key generation (default approach)
var random_seed: [32]u8 = undefined;
std.crypto.random.bytes(&random_seed);
var keypair = try sig.generateKeyPair(allocator, &random_seed, 0, 0);

// Deterministic key generation from a known seed
const deterministic_seed: [32]u8 = .{1} ** 32; // Use a proper KDF in production!
var keypair2 = try sig.generateKeyPair(allocator, &deterministic_seed, 0, 0);
// Same seed will always generate the same keypair

// Epoch management example
var keypair_limited = try sig.generateKeyPair(
    allocator, 
    &seed,
    100,    // activation_epoch: first valid epoch is 100
    50      // num_active_epochs: can sign epochs 100-149
);

Using SHA3 Hash Function

// Initialize with SHA3-256 instead of Poseidon2
const params = hash_zig.Parameters.initWithSha3(.lifetime_2_16);

// Everything else works the same way
var sig = try hash_zig.HashSignature.init(allocator, params);
defer sig.deinit();

Hash Function Selection

// Poseidon2 with hypercube parameters (default) - optimized for ZK proof systems
const params_p2 = hash_zig.Parameters.initHypercube(.lifetime_2_16);

// SHA3-256 - NIST standard
const params_sha3 = hash_zig.Parameters.initWithSha3(.lifetime_2_16);

// Default parameters (Poseidon2 with lifetime_2_16)
const params_default = hash_zig.Parameters.initDefault();

Different Key Lifetimes

// lifetime_2_10: 2^10 = 1,024 signatures
const params_short = hash_zig.Parameters.initHypercube(.lifetime_2_10);

// lifetime_2_16: 2^16 = 65,536 signatures (default)
const params_medium = hash_zig.Parameters.initHypercube(.lifetime_2_16);

// lifetime_2_18: 2^18 = 262,144 signatures (for benchmarking against Rust impl)
const params_benchmark = hash_zig.Parameters.initHypercube(.lifetime_2_18);

// lifetime_2_20: 2^20 = 1,048,576 signatures
const params_long = hash_zig.Parameters.initHypercube(.lifetime_2_20);

// lifetime_2_28: 2^28 = 268,435,456 signatures
const params_very_long = hash_zig.Parameters.initHypercube(.lifetime_2_28);

// lifetime_2_32: 2^32 = 4,294,967,296 signatures
const params_extreme = hash_zig.Parameters.initHypercube(.lifetime_2_32);

βš™οΈ Configuration

Security Parameters

Using hypercube parameters as specified in hypercube-hashsig-parameters:

Parameter Value Notes
Security Level 128-bit Post-quantum secure
Hash Output 32 bytes 256-bit hash for 128-bit security
Encoding Binary Incomparable binary encoding
Winternitz w 3 Chain length 8 (2^3 = 8)
Number of Chains 64 Hypercube parameter specification
Chain Length 8 Winternitz parameter w=3
Poseidon2 Width 16 Rust-compatible (external_rounds=8, internal_rounds=20, sbox_degree=3)

Note: These parameters use the hypercube configuration (64 chains of length 8) as specified in the hypercube-hashsig-parameters repository, with Rust-compatible Poseidon2 parameters for interoperability.

Hash Functions

Function Security Output Size Use Case
Poseidon2 128-bit 32 bytes ZK proofs, arithmetic circuits
SHA3-256 128-bit 32 bytes NIST standard, general crypto

Both hash functions provide 128-bit post-quantum security with 32-byte (256-bit) output.

Key Lifetimes

Lifetime Tree Height Max Signatures Memory Required*
lifetime_2_10 10 1,024 ~32 KB
lifetime_2_16 16 65,536 ~2 MB
lifetime_2_18 18 262,144 ~8.4 MB
lifetime_2_20 20 1,048,576 ~33 MB
lifetime_2_28 28 268,435,456 ~8.6 GB
lifetime_2_32 32 4,294,967,296 ~137 GB

*Memory estimates based on 32-byte hashes and cached leaves. Actual memory usage may vary.

πŸ—οΈ Architecture

hash-zig/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ root.zig              # Main module entry point
β”‚   β”œβ”€β”€ params.zig            # Configuration and parameters
β”‚   β”œβ”€β”€ poseidon2/
β”‚   β”‚   β”œβ”€β”€ fields/
β”‚   β”‚   β”‚   β”œβ”€β”€ generic_montgomery.zig   # Generic 31-bit Montgomery arithmetic
β”‚   β”‚   β”‚   └── koalabear/montgomery.zig # KoalaBear field instance
β”‚   β”‚   β”œβ”€β”€ instances/koalabear16.zig    # Width-16 Poseidon2 instance (plonky3 constants)
β”‚   β”‚   β”œβ”€β”€ poseidon2.zig                # Rust-compatible Poseidon2 implementation
β”‚   β”‚   └── generic_poseidon2.zig        # Generic Poseidon2 constructor (Montgomery)
β”‚   β”œβ”€β”€ sha3.zig              # SHA3 hash implementation
β”‚   β”œβ”€β”€ encoding.zig          # Incomparable encodings
β”‚   β”œβ”€β”€ tweakable_hash.zig    # Domain-separated hashing
β”‚   β”œβ”€β”€ winternitz.zig        # Winternitz OTS
β”‚   β”œβ”€β”€ merkle.zig            # Merkle tree construction
β”‚   └── signature.zig         # Main signature scheme
β”œβ”€β”€ examples/
β”‚   └── basic_usage.zig
β”œβ”€β”€ test/
β”‚   └── integration_test.zig
└── build.zig

Key Components (Matching Rust Implementation)

  • Poseidon2: Arithmetic hash over KoalaBear (31‑bit) with Montgomery reduction; constants match plonky3
  • SHA3-256: NIST-standardized Keccak-based hash for general-purpose cryptography
  • Winternitz OTS: One-time signature with 64 chains of length 8 (w=3) using hypercube parameters
  • Merkle Tree: Full binary tree storage (all nodes) with authentication path generation
  • Binary Encoding: Incomparable binary encoding with randomness (rho) for security
  • PRF-Based Key Derivation: On-demand OTS key generation from 32-byte PRF key
  • Epoch Management: Flexible key lifetime with activation_epoch and num_active_epochs support
  • Parallel Key Generation: Multi-threaded with atomic job queues (~3x speedup on 8-core M2)

πŸ“Š Performance

Benchmarking Against Reference Implementation

This implementation is benchmarked against the reference Rust implementation using the hash-sig-benchmarks suite.

The benchmark repository provides:

  • Automated comparison: Side-by-side performance testing of Rust vs Zig implementations
  • Fair parameters: Both implementations use identical hypercube parameters (w=3, 64 chains, Poseidon2)
  • Statistical analysis: Multiple iterations with mean, median, and standard deviation
  • Reproducible results: Standalone wrappers for each implementation ensure consistent testing

Benchmark the implementations yourself:

git clone https://github.com/ch4r10t33r/hash-sig-benchmarks.git
cd hash-sig-benchmarks
python3 benchmark.py 3  # Run 3 iterations

The benchmark suite automatically:

  1. Clones both hash-sig (Rust) and hash-zig (Zig)
  2. Builds standalone benchmark wrappers for each
  3. Runs key generation benchmarks with identical parameters
  4. Compares and reports performance differences

Actual Benchmarks

Measured on Apple M2 with Zig 0.14.1, using Poseidon2 hash and level_128 security:

Core Operations (lifetime_2_10: 1,024 signatures)

Operation Time Notes
Key Generation ~110 seconds Parallel multi-threaded, generates 1024 leaves + full tree (2047 nodes)
Sign ~370 ms Derives OTS keys from PRF, includes encoding randomness
Verify ~93 ms Reconstructs leaf, verifies Merkle path

Key Sizes (Matching Rust):

  • Public Key: 32 bytes (Merkle root) + Parameters struct
  • Secret Key: 32 bytes (PRF key) + Full tree (2047 nodes Γ— 32 bytes) + Epoch info
  • Signature: Auth path (10 Γ— 32 bytes) + OTS hashes (64 Γ— 32 bytes) + rho (32 bytes)

Performance Notes:

  • Using hypercube parameters (64 chains of length 8, w=3) from hypercube-hashsig-parameters
  • PRF-based key derivation: Keys derived on-demand during signing (not pre-stored)
  • Full tree storage: All 2047 tree nodes stored for fast auth path generation
  • Using Rust-compatible Poseidon2 (width=16) for interoperability
  • Optimized with inline hints for field arithmetic operations (~23% improvement)
  • Parallel key generation uses all available CPU cores automatically
  • Falls back to sequential mode for small workloads (< 64 leaves)
  • Speedup scales with CPU core count (tested on M2 with ~3x improvement over sequential)
  • Three levels of parallelization: leaf generation, WOTS chains, and Merkle tree construction
  • Fast verification (~25ms) thanks to shorter chain length (8 vs 256)
  • Benchmark against Rust: Use hash-sig-benchmarks for head-to-head comparison

Projected Key Generation Times for All Lifetimes

All projections based on Apple M2 Mac (8 cores) with parallel implementation - actual times will vary by hardware.

Lifetime Signatures Tree Height Estimated Time* Memory Required
lifetime_2_10 1,024 10 ~7 min (measured on M2, hypercube parameters) ~33 KB
lifetime_2_16 65,536 16 ~34 minutes ~2.1 MB
lifetime_2_18 262,144 18 ~2.2 hours ~8.4 MB
lifetime_2_20 1,048,576 20 ~9 hours ~34 MB
lifetime_2_28 268,435,456 28 ~97 days ~8.6 GB
lifetime_2_32 4,294,967,296 32 ~4.1 years ~137 GB

*Projected by linear scaling from M2 parallel measurements with hypercube parameters: (signatures / 1024) Γ— 7 min. Key generation scales O(n) with number of signatures. Performance will vary based on CPU core count and speed.

Sign/Verify Operations (All Lifetimes)

Operation Time Complexity
Sign ~50 ms O(log n) - constant across lifetimes
Verify ~25 ms O(log n) - constant across lifetimes

Note: Signing and verification times remain nearly constant across all lifetimes because they only process the authentication path (length = tree height). Only key generation scales with the number of signatures.

Performance Characteristics

  • Key Generation: O(n) where n = 2^tree_height (generates all OTS keypairs and caches leaves)
  • Signing: O(log n) with caching (generates OTS sig + retrieves auth path from cache)
  • Verification: O(log n) (derives OTS public key + verifies Merkle path)
  • Memory: O(n) for cached leaves (required for fast signing)

Optimization Tips

  1. Use appropriate lifetime for your use case
  2. Choose hash function based on requirements:
    • Poseidon2 for ZK-proof systems
    • SHA3 for NIST compliance and interoperability
  3. Batch key generation offline when possible
  4. Always persist signature state to prevent index reuse
  5. For maximum throughput use ReleaseFast, LTO, and run on CPUs with many cores
  6. Benchmark large lifetimes (β‰₯ 2^16) to leverage parallel scheduling best

πŸ”’ Security Considerations

⚠️ Critical Rules

  1. NEVER reuse a signature index - Each index must be used only once
    • Your application MUST track which indices have been used
    • Store the last used index persistently before generating each signature
    • The library does not enforce this - it's your responsibility!
  2. Protect the secret key - Use secure storage (encrypted, HSM, etc.)
  3. Verify signatures properly - Always check return values
  4. Plan key rotation - Generate new keypair before exhausting signatures

Security Properties

  • Post-quantum secure: Resistant to quantum attacks
  • Stateful: Requires tracking used indices (application responsibility)
  • Forward secure: Old signatures valid even if key compromised
  • One-time per index: Each tree index used once only

State Management (Application Responsibility)

Important: This library does NOT manage signature state. Your application MUST:

  1. Track the next available index - Start at 0, increment after each signature
  2. Persist state before signing - Save index to disk/database BEFORE calling sign()
  3. Never reuse an index - Reusing an index can compromise security
  4. Handle crashes gracefully - Use atomic writes or write-ahead logging

Example state management pattern:

// Pseudo-code for safe state management
fn signMessage(db: *Database, sig_scheme: *HashSignature, message: []const u8, secret_key: []const u8) !Signature {
    // 1. Get and increment index atomically
    const index = try db.getAndIncrementIndex();
    
    // 2. Persist the new index BEFORE signing
    try db.saveIndex(index + 1);
    try db.flush(); // Ensure it's on disk
    
    // 3. Now safe to sign
    return sig_scheme.sign(allocator, message, secret_key, index);
}

πŸ“š API Reference

Key Structures (Rust-Compatible)

The implementation matches Rust's GeneralizedXMSSSignatureScheme exactly:

// Public Key (matches Rust GeneralizedXMSSPublicKey)
pub const PublicKey = struct {
    root: []u8,              // Merkle root hash
    parameter: Parameters,   // Hash function parameters
};

// Secret Key (matches Rust GeneralizedXMSSSecretKey)
pub const SecretKey = struct {
    prf_key: [32]u8,            // PRF key for key derivation
    tree: [][]u8,               // Full Merkle tree (all nodes)
    tree_height: u32,
    parameter: Parameters,       // Hash function parameters
    activation_epoch: u64,       // First valid epoch
    num_active_epochs: u64,      // Number of valid epochs
};

// Signature (matches Rust GeneralizedXMSSSignature)
pub const Signature = struct {
    epoch: u64,              // Signature epoch/index
    auth_path: [][]u8,       // Merkle authentication path
    rho: [32]u8,            // Encoding randomness
    hashes: [][]u8,         // OTS signature values
};

Main API Functions

// Initialize signature scheme
var sig_scheme = try hash_zig.HashSignature.init(allocator, params);
defer sig_scheme.deinit();

// Generate keypair (Rust: key_gen)
// activation_epoch: first valid epoch (0 = start)
// num_active_epochs: number of epochs (0 = use full lifetime)
var keypair = try sig_scheme.generateKeyPair(
    allocator, 
    &seed,              // 32-byte seed
    0,                  // activation_epoch
    0                   // num_active_epochs (0 = all)
);
defer keypair.deinit(allocator);

// Sign a message (Rust: sign)
var signature = try sig_scheme.sign(
    allocator,
    message,            // Message to sign
    &keypair.secret_key, // Secret key reference
    epoch,              // Epoch index (0 to lifetime-1)
    &rng_seed          // RNG seed for encoding randomness
);
defer signature.deinit(allocator);

// Verify signature (Rust: verify)
const is_valid = try sig_scheme.verify(
    allocator,
    message,            // Message to verify
    signature,          // Signature to check
    &keypair.public_key // Public key reference
);

Parameters

// Poseidon2 with hypercube parameters (recommended)
const params = hash_zig.Parameters.initHypercube(.lifetime_2_16);

// SHA3-256
const params_sha3 = hash_zig.Parameters.initWithSha3(.lifetime_2_16);

// Default (Poseidon2, lifetime_2_16, 128-bit security)
const params_default = hash_zig.Parameters.initDefault();

Enums

pub const SecurityLevel = enum { level_128 }; // Only 128-bit supported
pub const HashFunction = enum { poseidon2, sha3 };
pub const KeyLifetime = enum { 
    lifetime_2_10,   // 1,024 signatures
    lifetime_2_16,   // 65,536 signatures
    lifetime_2_18,   // 262,144 signatures
    lifetime_2_20,   // 1,048,576 signatures
    lifetime_2_28,   // 268,435,456 signatures
    lifetime_2_32    // 4,294,967,296 signatures
};
pub const EncodingType = enum { binary }; // Only binary encoding supported

πŸ§ͺ Testing

Run All Tests

zig build test

Run Linter

zig build lint

Note: The linter (zlinter) is a dev-time tool for this repository. Consumers of hash-zig do not need to depend on zlinter unless they want to run our lint target in their own CI.

Build Library

zig build

Run Example

zig build example

Generate Documentation

zig build docs

This will generate HTML documentation in zig-out/docs/. Open zig-out/docs/index.html in your browser to view the API documentation.

Test Examples

Poseidon2:

test "poseidon2 hashing" {
    const allocator = std.testing.allocator;
    const params = hash_zig.Parameters.initHypercube(.lifetime_2_16);
    
    var hash = try hash_zig.TweakableHash.init(allocator, params);
    defer hash.deinit();
    
    const result = try hash.hash(allocator, "test data", 0);
    defer allocator.free(result);
    
    try std.testing.expect(result.len == 32);
}

SHA3:

test "sha3 hashing" {
    const allocator = std.testing.allocator;
    const params = hash_zig.Parameters.initWithSha3(.lifetime_2_16);
    
    var hash = try hash_zig.TweakableHash.init(allocator, params);
    defer hash.deinit();
    
    const result = try hash.hash(allocator, "test data", 0);
    defer allocator.free(result);
    
    try std.testing.expect(result.len == 32); // SHA3-256
}

Comparison:

test "compare hash functions" {
    const allocator = std.testing.allocator;
    
    // Poseidon2
    const params_p2 = hash_zig.Parameters.initHypercube(.lifetime_2_16);
    var hash_p2 = try hash_zig.TweakableHash.init(allocator, params_p2);
    defer hash_p2.deinit();
    
    // SHA3-256
    const params_sha3 = hash_zig.Parameters.initWithSha3(.lifetime_2_16);
    var hash_sha3 = try hash_zig.TweakableHash.init(allocator, params_sha3);
    defer hash_sha3.deinit();
    
    const data = "test";
    const h1 = try hash_p2.hash(allocator, data, 0);
    defer allocator.free(h1);
    const h2 = try hash_sha3.hash(allocator, data, 0);
    defer allocator.free(h2);
    
    // Different hash functions produce different outputs
    try std.testing.expect(!std.mem.eql(u8, h1, h2));
}

🀝 Contributing

Contributions welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Write tests for changes
  4. Ensure tests pass (zig build test)
  5. Run linter (zig build lint)
  6. Open a Pull Request

CI/CD

GitHub Actions automatically runs on pushes/PRs to main, master, or develop:

  • Linting using zlinter
  • Tests on Ubuntu, macOS, Windows
  • Uses Zig 0.14.1 (required for zlinter compatibility)

See .github/workflows/ci.yml for details.

Note: The project currently requires Zig 0.14.1 because zlinter only supports the 0.14.x branch. Once zlinter adds support for Zig 0.15+, we'll update to the latest version.

πŸ› Known Issues

  • Large tree generation (2^28+) requires significant time and memory resources
  • No hypertree optimization for very large lifetimes
  • Performance benchmarks are hardware-specific (tested only on M2 Mac)

πŸ“„ License

Apache License 2.0 - see LICENSE file.

πŸ™ Acknowledgments

πŸ“§ Contact


⚠️ IMPORTANT DISCLAIMER: This is a prototype implementation for research and experimentation. This code has NOT been audited and should NOT be used in production systems. Applications using this library MUST implement proper state management to prevent signature index reuse - the library does not enforce this.

About

A pure zig implementation of hash based signatures inspired from the rust implementation https://github.com/b-wagn/hash-sig

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages