bump package id component to 32 bits

and to make the base64 round even, bump sha256 to 200 bits (up from 192)
This commit is contained in:
Andrew Kelley 2025-02-25 17:58:57 -08:00
parent 0fc7c9f57c
commit ea516f0e81
4 changed files with 26 additions and 27 deletions

View file

@ -12,5 +12,5 @@
}, },
}, },
.paths = .{""}, .paths = .{""},
.nonce = 0xc1ce10810000f013, .nonce = 0xc1ce108124179e16,
} }

View file

@ -22,9 +22,11 @@ Zig package namespace.
Must be a valid bare Zig identifier (don't `@` me), limited to 32 bytes. Must be a valid bare Zig identifier (don't `@` me), limited to 32 bytes.
Together with `nonce`, this represents a globally unique package identifier.
### `nonce` ### `nonce`
Together with name, this represents a globally unique package identifier. This Together with `name`, this represents a globally unique package identifier. This
field is auto-initialized by the toolchain when the package is first created, field is auto-initialized by the toolchain when the package is first created,
and then *never changes*. This allows Zig to unambiguously detect when one and then *never changes*. This allows Zig to unambiguously detect when one
package is an updated version of another. package is an updated version of another.
@ -34,14 +36,14 @@ project is still maintained. Otherwise, the fork is *hostile*, attempting to
take control over the original project's identity. The nonce can be regenerated take control over the original project's identity. The nonce can be regenerated
by deleting the field and running `zig build`. by deleting the field and running `zig build`.
This 64-bit integer is the combination of a 16-bit id component, a 32-bit This 64-bit integer is the combination of a 32-bit id component and a 32-bit
checksum, and 16 bits of reserved zeroes. checksum.
The id component within the nonce has these restrictions: The id component within the nonce has these restrictions:
`0x0000` is reserved for legacy packages. `0x00000000` is reserved for legacy packages.
`0xffff` is reserved to represent "naked" packages. `0xffffffff` is reserved to represent "naked" packages.
The checksum is computed from `name` and serves to protect Zig users from The checksum is computed from `name` and serves to protect Zig users from
accidental id collisions. accidental id collisions.

View file

@ -11,20 +11,19 @@ pub const multihash_hex_digest_len = 2 * multihash_len;
pub const MultiHashHexDigest = [multihash_hex_digest_len]u8; pub const MultiHashHexDigest = [multihash_hex_digest_len]u8;
pub const Nonce = packed struct(u64) { pub const Nonce = packed struct(u64) {
id: u16, id: u32,
reserved: u16 = 0,
checksum: u32, checksum: u32,
pub fn generate(name: []const u8) Nonce { pub fn generate(name: []const u8) Nonce {
return .{ return .{
.id = std.crypto.random.intRangeLessThan(u16, 0x0001, 0xffff), .id = std.crypto.random.intRangeLessThan(u32, 1, 0xffffffff),
.checksum = std.hash.Crc32.hash(name), .checksum = std.hash.Crc32.hash(name),
}; };
} }
pub fn validate(n: Nonce, name: []const u8) bool { pub fn validate(n: Nonce, name: []const u8) bool {
switch (n.id) { switch (n.id) {
0x0000, 0xffff => return false, 0x00000000, 0xffffffff => return false,
else => return std.hash.Crc32.hash(name) == n.checksum, else => return std.hash.Crc32.hash(name) == n.checksum,
} }
} }
@ -54,8 +53,8 @@ pub const Hash = struct {
pub const Algo = std.crypto.hash.sha2.Sha256; pub const Algo = std.crypto.hash.sha2.Sha256;
pub const Digest = [Algo.digest_length]u8; pub const Digest = [Algo.digest_length]u8;
/// Example: "nnnn-vvvv-hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh" /// Example: "nnnn-vvvv-hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh"
pub const max_len = 32 + 1 + 32 + 1 + (16 + 32 + 192) / 6; pub const max_len = 32 + 1 + 32 + 1 + (32 + 32 + 200) / 6;
pub fn fromSlice(s: []const u8) Hash { pub fn fromSlice(s: []const u8) Hash {
assert(s.len <= max_len); assert(s.len <= max_len);
@ -96,14 +95,12 @@ pub const Hash = struct {
/// bytes and assumed be a valid zig identifier /// bytes and assumed be a valid zig identifier
/// * semver is the version field from build.zig.zon, asserted to be at /// * semver is the version field from build.zig.zon, asserted to be at
/// most 32 bytes /// most 32 bytes
/// * hashplus is the following 39-byte array, base64 encoded using -_ to make /// * hashplus is the following 33-byte array, base64 encoded using -_ to make
/// it filesystem safe: /// it filesystem safe:
/// - (2 bytes) LE u16 Package ID /// - (4 bytes) LE u32 Package ID
/// - (4 bytes) LE u32 total decompressed size in bytes, overflow saturated /// - (4 bytes) LE u32 total decompressed size in bytes, overflow saturated
/// - (24 bytes) truncated SHA-256 digest of hashed files of the package /// - (25 bytes) truncated SHA-256 digest of hashed files of the package
/// pub fn init(digest: Digest, name: []const u8, ver: []const u8, id: u32, size: u32) Hash {
/// example: "nasm-2.16.1-3-AAD_ZlwACpGU-c3QXp_yNyn07Q5U9Rq-Cb1ur2G1"
pub fn init(digest: Digest, name: []const u8, ver: []const u8, id: u16, size: u32) Hash {
assert(name.len <= 32); assert(name.len <= 32);
assert(ver.len <= 32); assert(ver.len <= 32);
var result: Hash = undefined; var result: Hash = undefined;
@ -112,11 +109,11 @@ pub const Hash = struct {
buf.appendAssumeCapacity('-'); buf.appendAssumeCapacity('-');
buf.appendSliceAssumeCapacity(ver); buf.appendSliceAssumeCapacity(ver);
buf.appendAssumeCapacity('-'); buf.appendAssumeCapacity('-');
var hashplus: [30]u8 = undefined; var hashplus: [33]u8 = undefined;
std.mem.writeInt(u16, hashplus[0..2], id, .little); std.mem.writeInt(u32, hashplus[0..4], id, .little);
std.mem.writeInt(u32, hashplus[2..6], size, .little); std.mem.writeInt(u32, hashplus[4..8], size, .little);
hashplus[6..].* = digest[0..24].*; hashplus[8..].* = digest[0..25].*;
_ = std.base64.url_safe_no_pad.Encoder.encode(buf.addManyAsArrayAssumeCapacity(40), &hashplus); _ = std.base64.url_safe_no_pad.Encoder.encode(buf.addManyAsArrayAssumeCapacity(44), &hashplus);
@memset(buf.unusedCapacitySlice(), 0); @memset(buf.unusedCapacitySlice(), 0);
return result; return result;
} }
@ -194,8 +191,8 @@ test Hash {
0xc7, 0xf5, 0x71, 0xb7, 0xb4, 0xe7, 0x6f, 0x3c, 0xdb, 0x87, 0x7a, 0x7f, 0xdd, 0xf9, 0x77, 0x87, 0xc7, 0xf5, 0x71, 0xb7, 0xb4, 0xe7, 0x6f, 0x3c, 0xdb, 0x87, 0x7a, 0x7f, 0xdd, 0xf9, 0x77, 0x87,
0x9d, 0xd3, 0x86, 0xfa, 0x73, 0x57, 0x9a, 0xf7, 0x9d, 0x1e, 0xdb, 0x8f, 0x3a, 0xd9, 0xbd, 0x9f, 0x9d, 0xd3, 0x86, 0xfa, 0x73, 0x57, 0x9a, 0xf7, 0x9d, 0x1e, 0xdb, 0x8f, 0x3a, 0xd9, 0xbd, 0x9f,
}; };
const result: Hash = .init(example_digest, "nasm", "2.16.1-2", 0xcafe, 10 * 1024 * 1024); const result: Hash = .init(example_digest, "nasm", "2.16.1-3", 0xcafebabe, 10 * 1024 * 1024);
try std.testing.expectEqualStrings("nasm-2.16.1-2-_soAAKAAx_Vxt7Tnbzzbh3p_3fl3h53ThvpzV5r3", result.toSlice()); try std.testing.expectEqualStrings("nasm-2.16.1-3-vrr-ygAAoADH9XG3tOdvPNuHen_d-XeHndOG-nNXmved", result.toSlice());
} }
test { test {

View file

@ -36,7 +36,7 @@ pub const ErrorMessage = struct {
}; };
name: []const u8, name: []const u8,
id: u16, id: u32,
version: std.SemanticVersion, version: std.SemanticVersion,
version_node: Ast.Node.Index, version_node: Ast.Node.Index,
dependencies: std.StringArrayHashMapUnmanaged(Dependency), dependencies: std.StringArrayHashMapUnmanaged(Dependency),
@ -149,7 +149,7 @@ const Parse = struct {
errors: std.ArrayListUnmanaged(ErrorMessage), errors: std.ArrayListUnmanaged(ErrorMessage),
name: []const u8, name: []const u8,
id: u16, id: u32,
version: std.SemanticVersion, version: std.SemanticVersion,
version_node: Ast.Node.Index, version_node: Ast.Node.Index,
dependencies: std.StringArrayHashMapUnmanaged(Dependency), dependencies: std.StringArrayHashMapUnmanaged(Dependency),