Vanity package hashes

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 :slight_smile:

3 Likes

It’s a real shame there aren’t enough bytes to encode dQw4w9WgXcQ.

1 Like