https://github.com/ch4r10t33r/hash-zig
A pure zig implementation of hash based signatures inspired from the rust implementation https://github.com/b-wagn/hash-sig
https://github.com/ch4r10t33r/hash-zig
hash-sig lean-ethereum zig
Last synced: 4 months ago
JSON representation
A pure zig implementation of hash based signatures inspired from the rust implementation https://github.com/b-wagn/hash-sig
- Host: GitHub
- URL: https://github.com/ch4r10t33r/hash-zig
- Owner: ch4r10t33r
- License: apache-2.0
- Created: 2025-09-30T10:15:56.000Z (4 months ago)
- Default Branch: master
- Last Pushed: 2025-10-06T21:55:46.000Z (4 months ago)
- Last Synced: 2025-10-06T23:02:48.336Z (4 months ago)
- Topics: hash-sig, lean-ethereum, zig
- Language: Zig
- Homepage:
- Size: 205 KB
- Stars: 4
- Watchers: 1
- Forks: 0
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-zig - ch4r10t33r/hash-zig - wagn/hash-sig (Web3 Framework / Zigged Project)
README
# hash-zig
[](https://github.com/ch4r10t33r/hash-zig/actions/workflows/ci.yml)
[](https://ziglang.org/)
[](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](https://eprint.iacr.org/2025/055.pdf), with **exact compatibility** with the [hash-sig](https://github.com/b-wagn/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](https://github.com/b-wagn/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](https://github.com/b-wagn/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](#installation)
- [Quick Start](#quick-start)
- [Programs](#programs)
- [Usage](#usage)
- [Configuration](#configuration)
- [Architecture](#architecture)
- [Performance](#performance)
- [Security Considerations](#security-considerations)
- [API Reference](#api-reference)
- [Testing](#testing)
- [Contributing](#contributing)
- [License](#license)
## π Installation
### Using Zig Package Manager
Add to your `build.zig.zon`:
```zig
.{
.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`:
```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
```bash
git clone https://github.com/ch4r10t33r/hash-zig.git
cd hash-zig
zig build test
```
## β‘ Quick Start
```zig
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
```bash
# 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
```zig
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
```zig
// 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
```zig
// 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
```zig
// 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
```zig
// 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](https://github.com/b-wagn/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](https://github.com/b-wagn/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](https://github.com/ch4r10t33r/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**:
```bash
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](https://github.com/b-wagn/hash-sig) (Rust) and [hash-zig](https://github.com/ch4r10t33r/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](https://github.com/b-wagn/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](https://github.com/ch4r10t33r/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:
```zig
// 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:
```zig
// 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
```zig
// 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
```zig
// 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
```zig
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
```bash
zig build test
```
### Run Linter
```bash
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
```bash
zig build
```
### Run Example
```bash
zig build example
```
### Generate Documentation
```bash
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:**
```zig
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:**
```zig
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:**
```zig
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](https://github.com/kurtwagner/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](LICENSE) file.
## π Acknowledgments
- Inspired by [Rust implementation](https://github.com/b-wagn/hash-sig)
- Framework from [hash-sig paper](https://eprint.iacr.org/2025/055.pdf)
- Poseidon2 spec from [Poseidon2 paper](https://eprint.iacr.org/2023/323.pdf)
## π§ Contact
- Issues: [GitHub Issues](https://github.com/ch4r10t33r/hash-zig/issues)
- Discussions: [GitHub Discussions](https://github.com/ch4r10t33r/hash-zig/discussions)
---
**β οΈ 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.