nx_uuid5.nx source
↩ module page · 126 lines · 4293 B
1// uuid5.nx -- UUIDv5 (SHA-1 namespace + name -> UUID).
2//
3// RFC 4122 ยง4.3. Produces a DETERMINISTIC 128-bit UUID from a
4// namespace UUID + an arbitrary name. Same (ns, name) always
5// yields the same UUID. Used for:
6// - stable IDs derived from URLs (content hashing)
7// - deterministic object IDs in distributed systems
8// - test fixture IDs that don't drift between runs
9// - git-style content addressing without exposing the hash
10//
11// Algorithm:
12// hash = SHA-1(namespace_uuid_bytes || name_bytes)
13// uuid[0..16] = hash[0..16] (take first 16 bytes)
14// uuid[6] = (uuid[6] & 0x0F) | 0x50 (version = 5)
15// uuid[8] = (uuid[8] & 0x3F) | 0x80 (variant = RFC 4122)
16//
17// The RFC defines four standard namespace UUIDs for DNS, URL,
18// OID, and X.500 -- we expose them as byte constants. Callers
19// needing their own namespace mint a v4 UUID (uuid_v4_bytes)
20// once and reuse it forever.
21//
22// Invariants:
23// U5 1 Output is 16 bytes (not the hyphenated string; use
24// uuid_format from uuid.nx to get the string form).
25// U5 2 Deterministic: pure function of (ns, name).
26// U5 3 Byte-for-byte compatible with Python's uuid.uuid5,
27// Node's uuid library, java.util.UUID.nameUUIDFromBytes.
28
29// nx_safety_envelope:
30// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
31// sil_target: SIL1
32// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
33// verdict: NOT_YET_EVALUATED
34
35import "nx_syscalls.nx"
36import "nx_sha1.nx"
37
38// Standard namespaces (RFC 4122 Appendix C). All four are
39// 16 bytes; we expose them as function-returned pointers so
40// callers can just `memcpy` or pass them to uuid_v5.
41func uuid_ns_dns() -> *u8 {
42 // 6ba7b810-9dad-11d1-80b4-00c04fd430c8
43 return "\x6b\xa7\xb8\x10\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8"
44}
45func uuid_ns_url() -> *u8 {
46 // 6ba7b811-9dad-11d1-80b4-00c04fd430c8
47 return "\x6b\xa7\xb8\x11\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8"
48}
49func uuid_ns_oid() -> *u8 {
50 // 6ba7b812-9dad-11d1-80b4-00c04fd430c8
51 return "\x6b\xa7\xb8\x12\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8"
52}
53func uuid_ns_x500() -> *u8 {
54 // 6ba7b814-9dad-11d1-80b4-00c04fd430c8
55 return "\x6b\xa7\xb8\x14\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8"
56}
57
58// Produce a 16-byte UUIDv5. namespace must be 16 bytes; name
59// is an arbitrary byte sequence.
60func uuid_v5_bytes(namespace: *u8, name: *u8, name_len: i64,
61 out: *u8) -> i64 {
62 // Concatenate namespace || name into a scratch buffer.
63 let buf_len: i64 = 16 + name_len
64 let buf: *u8 = sys_mmap(buf_len + 16)
65 var i: i64 = 0
66 while i < 16 {
67 buf[i] = namespace[i]
68 i = i + 1
69 }
70 i = 0
71 while i < name_len {
72 buf[16 + i] = name[i]
73 i = i + 1
74 }
75
76 // SHA-1 -> 20 bytes; we keep first 16.
77 let hash: *u8 = sys_mmap(32)
78 sha1(buf, buf_len, hash)
79
80 i = 0
81 while i < 16 {
82 out[i] = hash[i]
83 i = i + 1
84 }
85
86 // Stamp version (5) and variant (RFC 4122 = 10xx).
87 out[6] = (out[6] & 0x0F) | 0x50
88 out[8] = (out[8] & 0x3F) | 0x80
89 return 0
90}
91
92// Compile-only smoke. RFC 4122 Appendix B worked example:
93// uuidv5(DNS, "python.org") = 886313e1-3b8a-5372-9b90-0c9aee199e5d
94func main() -> i64 {
95 let out: *u8 = sys_mmap(32)
96 uuid_v5_bytes(uuid_ns_dns(), "python.org", 10, out)
97
98 // Check first byte (0x88), version nibble, variant nibble.
99 if out[0] != 0x88 { return 1 }
100 if out[1] != 0x63 { return 2 }
101 // version nibble is high half of out[6].
102 if ((out[6] >> 4) & 0xF) != 5 { return 3 }
103 // variant is top two bits of out[8]: must be 10.
104 if ((out[8] >> 6) & 0x3) != 2 { return 4 }
105
106 // Determinism: same input -> same output.
107 let out2: *u8 = sys_mmap(32)
108 uuid_v5_bytes(uuid_ns_dns(), "python.org", 10, out2)
109 var i: i64 = 0
110 while i < 16 {
111 if out[i] != out2[i] { return 5 }
112 i = i + 1
113 }
114
115 // Different name -> different UUID.
116 let out3: *u8 = sys_mmap(32)
117 uuid_v5_bytes(uuid_ns_dns(), "ruby-lang.org", 13, out3)
118 var diff: i64 = 0
119 i = 0
120 while i < 16 {
121 if out[i] != out3[i] { diff = 1; break }
122 i = i + 1
123 }
124 if diff != 1 { return 6 }
125 return 0
126}