jedisct1/zig-kangarootwelve
Kangarootwelve implementation in Zig.
A pure Zig implementation of the KangarooTwelve (K12) cryptographic hash function with support for both sequential and parallel processing.
KangarooTwelve is a fast, secure cryptographic hash function based on Keccak (SHA-3). It uses a tree-hashing mode on top of TurboSHAKE, providing both high security and excellent performance, especially on large inputs. K12 supports arbitrary-length output and optional customization strings.
This implementation provides both KT128 (based on TurboSHAKE128) and KT256 (based on TurboSHAKE256).
Add this package to your build.zig.zon
:
.dependencies = .{
.kangarootwelve = .{
.url = "https://github.com/jedisct1/zig-kangarootwelve/archive/refs/tags/v0.0.1.tar.gz",
.hash = "...",
},
},
Then in your build.zig
:
const kangarootwelve = b.dependency("kangarootwelve", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("kangarootwelve", kangarootwelve.module("kangarootwelve"));
const std = @import("std");
const kangarootwelve = @import("kangarootwelve");
// Create a KT128 hasher with 32-byte output
const KT128_32 = kangarootwelve.KT128(32);
var output: [32]u8 = undefined;
const message = "Hello, KangarooTwelve!";
// Hash with no customization string
try KT128_32.hash(message, null, &output);
// Or with a customization string
try KT128_32.hash(message, "my-app-v1", &output);
// 64-byte output
const KT128_64 = kangarootwelve.KT128(64);
var output: [64]u8 = undefined;
try KT128_64.hash(message, null, &output);
// Any output length you need
const KT128_128 = kangarootwelve.KT128(128);
var large_output: [128]u8 = undefined;
try KT128_128.hash(message, null, &large_output);
For large inputs (>50MB), parallel processing can significantly improve performance:
const allocator = std.heap.page_allocator;
const large_data = try allocator.alloc(u8, 100 * 1024 * 1024); // 100MB
defer allocator.free(large_data);
var output: [32]u8 = undefined;
try KT128_32.hashParallel(large_data, null, &output, allocator);
The implementation automatically uses sequential processing for smaller inputs (≤50MB) to avoid thread pool overhead.
const KT256_64 = kangarootwelve.KT256(64);
var output: [64]u8 = undefined;
try KT256_64.hash(message, null, &output);
KT128(comptime output_len: usize)
Returns a type with the following methods:
hash(message: []const u8, customization: ?[]const u8, out: *[output_len]u8) !void
Hashes a message using sequential processing.
message
: Input data to hashcustomization
: Optional customization string (can be null
or empty)out
: Output buffer of the specified lengthhashParallel(message: []const u8, customization: ?[]const u8, out: *[output_len]u8, allocator: std.mem.Allocator) !void
Hashes a message with parallel chunk processing. Automatically falls back to sequential processing for inputs ≤50MB.
message
: Input data to hashcustomization
: Optional customization string (can be null
or empty)out
: Output buffer of the specified lengthallocator
: Memory allocator for thread pool and intermediate buffersKT256(comptime output_len: usize)
Same API as KT128
, but uses TurboSHAKE256 internally for higher security margin.
The implementation includes optimizations for both small and large inputs:
Benchmark your specific use case with:
zig build bench
This runs benchmarks on various input sizes and compares sequential vs parallel performance.
Build the library:
zig build
Run tests:
zig build test
Run benchmarks:
zig build bench
Build with optimizations:
zig build -Doptimize=ReleaseFast