ch4r10t33r/hash-zig
A pure zig implementation of hash based signatures inspired from the rust implementation https://github.com/b-wagn/hash-sig
master
A pure Zig implementation of hash-based signatures using Poseidon2 and SHA3 hash functions with incomparable encodings. This library implements XMSS-like signatures based on the framework from this paper.
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"));
git clone https://github.com/ch4r10t33r/hash-zig.git
cd hash-zig
zig build test
const std = @import("std");
const hash_zig = @import("hash-zig");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Initialize with 128-bit security and medium lifetime (2^16 signatures)
const params = hash_zig.Parameters.init(.level_128, .lifetime_2_16);
var sig_scheme = try hash_zig.HashSignature.init(allocator, params);
defer sig_scheme.deinit();
// Generate keypair
var keypair = try sig_scheme.generateKeyPair(allocator);
defer keypair.deinit(allocator);
// Sign a message
const message = "Hello, hash-based signatures!";
var signature = try sig_scheme.sign(allocator, message, keypair.secret_key, 0);
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});
}
const hash_zig = @import("hash-zig");
// Configure parameters with Poseidon2 (default)
const params = hash_zig.Parameters.init(.level_128, .lifetime_2_16);
var sig = try hash_zig.HashSignature.init(allocator, params);
defer sig.deinit();
// Generate keys
var keypair = try sig.generateKeyPair(allocator);
defer keypair.deinit(allocator);
// Sign
var signature = try sig.sign(allocator, "message", keypair.secret_key, 0);
defer signature.deinit(allocator);
// Verify
const valid = try sig.verify(allocator, "message", signature, keypair.public_key);
// Initialize with SHA3-256 instead of Poseidon2
const params = hash_zig.Parameters.initWithSha3(.level_128, .lifetime_2_16);
// Everything else works the same way
var sig = try hash_zig.HashSignature.init(allocator, params);
defer sig.deinit();
// 128-bit security with Poseidon2
const params_128 = hash_zig.Parameters.init(.level_128, .lifetime_2_16);
// 192-bit security with Poseidon2
const params_192 = hash_zig.Parameters.init(.level_192, .lifetime_2_16);
// 256-bit security with Poseidon2
const params_256 = hash_zig.Parameters.init(.level_256, .lifetime_2_16);
// Or use SHA3
const params_sha3 = hash_zig.Parameters.initWithSha3(.level_128, .lifetime_2_16);
// lifetime_2_10: 2^10 = 1,024 signatures
const params_short = hash_zig.Parameters.init(.level_128, .lifetime_2_10);
// lifetime_2_16: 2^16 = 65,536 signatures (default)
const params_medium = hash_zig.Parameters.init(.level_128, .lifetime_2_16);
// lifetime_2_20: 2^20 = 1,048,576 signatures
const params_long = hash_zig.Parameters.init(.level_128, .lifetime_2_20);
// lifetime_2_28: 2^28 = 268,435,456 signatures
const params_very_long = hash_zig.Parameters.init(.level_128, .lifetime_2_28);
// lifetime_2_32: 2^32 = 4,294,967,296 signatures
const params_extreme = hash_zig.Parameters.init(.level_128, .lifetime_2_32);
Level | Hash Output | Encoding | Security Bits |
---|---|---|---|
level_128 | 32 bytes | binary | 128-bit |
level_192 | 48 bytes | ternary | 192-bit |
level_256 | 64 bytes | quaternary | 256-bit |
Function | Security | Output Size | Use Case |
---|---|---|---|
poseidon2_128 | 128-bit | 32 bytes | ZK proofs, arithmetic circuits |
poseidon2_192 | 192-bit | 48 bytes | ZK proofs, arithmetic circuits |
poseidon2_256 | 256-bit | 64 bytes | ZK proofs, arithmetic circuits |
sha3_256 | 128-bit | 32 bytes | NIST standard, general crypto |
sha3_384 | 192-bit | 48 bytes | NIST standard, general crypto |
sha3_512 | 256-bit | 64 bytes | NIST standard, general crypto |
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_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. For large lifetimes, hypertree optimization is automatically used.
hash-zig/
โโโ src/
โ โโโ root.zig # Main module entry point
โ โโโ params.zig # Configuration and parameters
โ โโโ poseidon2/
โ โ โโโ field.zig # BN254 field arithmetic
โ โ โโโ hash.zig # Poseidon2 implementation
โ โโโ 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
โ ๏ธ Benchmarks pending: Performance measurements have not been conducted yet. Values will vary based on hardware, hash function choice, and implementation optimizations.
Operation | Estimated |
---|---|
Key Generation (2^10) | TBD |
Sign | TBD |
Verify | TBD |
Public Key Size | 32-64 bytes |
Signature Size | ~2-4 KB |
// Poseidon2 (default)
const params = hash_zig.Parameters.init(.level_128, .lifetime_2_16);
// SHA3
const params_sha3 = hash_zig.Parameters.initWithSha3(.level_128, .lifetime_2_16);
// Default (Poseidon2, lifetime_2_16)
const params_default = hash_zig.Parameters.initDefault(.level_128);
pub const SecurityLevel = enum { level_128, level_192, level_256 };
pub const HashFunction = enum {
poseidon2_128, poseidon2_192, poseidon2_256,
sha3_256, sha3_384, sha3_512
};
pub const KeyLifetime = enum {
lifetime_2_10, // 1,024 signatures
lifetime_2_16, // 65,536 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, ternary, quaternary };
zig build test
zig build lint
zig build
zig build example
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.
Poseidon2:
test "poseidon2 hashing" {
const allocator = std.testing.allocator;
const params = hash_zig.Parameters.init(.level_128, .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(.level_128, .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.init(.level_128, .lifetime_2_16);
var hash_p2 = try hash_zig.TweakableHash.init(allocator, params_p2);
defer hash_p2.deinit();
// SHA3
const params_sha3 = hash_zig.Parameters.initWithSha3(.level_128, .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));
}
Contributions welcome! Please:
zig build test
)zig build lint
)GitHub Actions automatically runs on pushes/PRs to main
, master
, or develop
:
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.
โ ๏ธ Important: This is a research/prototype implementation. The signature verification currently does not perform full Merkle tree validation and should NOT be used in production.
Apache License 2.0 - see LICENSE file.
โ ๏ธ IMPORTANT DISCLAIMER: This is a prototype implementation for research and experimentation. The signature verification is incomplete (Merkle authentication paths not implemented). This code has NOT been audited and should NOT be used in production systems.