//! document id helpers. string ids are capped at 64 bytes, which an at-uri //! routinely exceeds; `hash` folds any string into 32 hex characters. the //! original then has to live in an attribute or an external store. const std = @import("std"); const Sha256 = std.crypto.hash.sha2.Sha256; pub const max_len = 64; pub fn fits(s: []const u8) bool { return s.len <= max_len; } /// first 128 bits of sha256, lowercase hex. pub fn hash(input: []const u8) [32]u8 { var digest: [32]u8 = undefined; Sha256.hash(input, &digest, .{}); return std.fmt.bytesToHex(digest[0..16], .lower); } test "hash is 32 lowercase hex chars, deterministic, input-sensitive" { const a = hash("at://did:plc:abc/app.bsky.feed.post/3kxyz"); const b = hash("at://did:plc:abc/app.bsky.feed.post/3kxyz"); const c = hash("at://did:plc:def/app.bsky.feed.post/3kxyz"); try std.testing.expectEqualSlices(u8, &a, &b); try std.testing.expect(!std.mem.eql(u8, &a, &c)); for (a) |ch| try std.testing.expect(std.ascii.isHex(ch) and !std.ascii.isUpper(ch)); try std.testing.expect(fits(&a)); try std.testing.expect(!fits(&@as([65]u8, @splat('x')))); } test "hash matches the value pub-search and coral already store" { var digest: [32]u8 = undefined; Sha256.hash("hello", &digest, .{}); const expected = std.fmt.bytesToHex(digest[0..16], .lower); try std.testing.expectEqualStrings(&expected, &hash("hello")); }