The Zig package fingerprint is a 64-bit unsigned integer, with its higher 32 bits representing a checksum computed from the packages name and its lower 32 bits being a unique integer identifier used to help disambiguate different packages with the same name.
The first 5⅓ characters of the third component of the package hash correspond directly to the identifier half of the fingerprint.
.{
.dependencies = .{
.my_package= .{
.url = "https://example.com/my_package.tar.gz",
.hash = "my_package-1.2.3-WmgeQ7IWAAB6fVCmpyDMAxGjw5ZQLpOKVADbqqiiCV0h",
// ~~~~~~
},
},
}
When you run zig init or zig build to (re)initialize your package’s fingerprint, Zig generates a random number for the identifier half. But if you have a decent grasp on the techical details you can of course instead craft an identifier by hand that base64url-encodes to a human-readable message up to 5 characters in length.
Included below is a tiny program that lets you do just this, hidden behind a <details> element in case you’d like to try and solve this puzzle yourself without hints.
vanity_hash.zig
// vanity_hash.zig
const std = @import("std");
pub fn main(init: std.process.Init) !void {
const args = try init.minimal.args.toSlice(init.arena.allocator());
if (args.len != 3) {
std.debug.print("usage: vanity_hash <package-name> <wanted-prefix>\n", .{});
return error.InvalidUsage;
}
const package_name = args[1];
if (!std.zig.isValidId(package_name) or package_name.len > 32) return error.InvalidPackageName;
const name_checksum: u64 = std.hash.Crc32.hash(package_name);
var encoded_prefix: [8]u8 = @splat('A');
@memcpy(encoded_prefix[0..@min(6, args[2].len)], args[2].ptr);
var decoded_prefix_bytes: [6]u8 = undefined;
std.base64.url_safe_no_pad.Decoder.decode(&decoded_prefix_bytes, &encoded_prefix) catch return error.InvalidWantedPrefix;
const decoded_prefix: u64 = std.mem.readInt(u32, decoded_prefix_bytes[0..4], .little);
if (decoded_prefix == 0 or decoded_prefix == 0xffffffff) return error.InvalidWantedPrefix;
const fingerprint = name_checksum << 32 | decoded_prefix;
var decoded_hashplus: [33]u8 = undefined;
init.io.random(&decoded_hashplus);
decoded_hashplus[0..4].* = decoded_prefix_bytes[0..4].*;
decoded_hashplus[6..8].* = @splat(0);
var encoded_hashplus: [44]u8 = undefined;
_ = std.base64.url_safe_no_pad.Encoder.encode(&encoded_hashplus, &decoded_hashplus);
var stdout = std.Io.File.stdout().writer(init.io, &.{});
try stdout.interface.print(
\\.{{
\\ .name = .{s},
\\ .fingerprint = 0x{x},
\\
\\ .example_hash = "{s}-1.2.3-{s}",
\\}}
\\
, .{ package_name, fingerprint, package_name, encoded_hashplus });
}
zig run vanity_hash.zig -- my_package hello
.{
.name = .my_package,
.fingerprint = 0x5491cbb9a065e985,
.example_hash = "my_package-1.2.3-helloGwTAAAVs-gfyAKQLT5hY41VolOcogt6727LLZoZ",
}
Use this knowledge responsibly! The purpose of the package fingerprint is to prevent accidental collisions when two packages in a dependency graph share the same name, which will become an important detail once version selection is implemented in the Zig package manager. If everyone in the world were to hand-pick 0xf88a4015 for the identifier half of their fingerprint it would render this safeguard ineffective ![]()