code wiki / (root) / uuid5.nx

uuid5.nx source

↩ module page · 130 lines · 5211 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 29import "syscalls.nx" 30// 2026-08-01 -- REPOINTED FROM sha1.nx TO nx_sha1.nx (sev-8 remedy; identical symbol set, drop-in). 31// UUID VERSION 5 IS SHA-1 BASED (RFC 4122 sec 4.3), so a wrong SHA-1 core means every v5 UUID this module 32// has ever produced is WRONG -- and v5 UUIDs are typically used as STABLE IDENTITIES, so incorrect values 33// may already be persisted in stores and referenced by other records. 34// ★I MISSED THIS MODULE IN BOTH EARLIER BLAST-RADIUS COUNTS. It does not import hmac_sha1.nx, so it never 35// appeared when I traced the HMAC family -- it reaches the broken core by its OWN direct import. 36// ★★★★★A CLOSURE HAS MORE THAN ONE ROOT: enumerating dependents of ONE entry point finds only the dependents 37// of that entry point. Ask "who imports the DEFECTIVE FILE", not "who imports the thing I was looking at". 38// ⚠CONSEQUENCE WORTH FLAGGING: fixing this CHANGES the UUIDs this module emits. That is correct -- they were 39// wrong -- but any already-persisted v5 UUIDs will not match newly-computed ones, so a data audit is owed. 40import "nx_sha1.nx" 41 42// Standard namespaces (RFC 4122 Appendix C). All four are 43// 16 bytes; we expose them as function-returned pointers so 44// callers can just `memcpy` or pass them to uuid_v5. 45func uuid_ns_dns() -> *u8 { 46 // 6ba7b810-9dad-11d1-80b4-00c04fd430c8 47 return "\x6b\xa7\xb8\x10\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8" 48} 49func uuid_ns_url() -> *u8 { 50 // 6ba7b811-9dad-11d1-80b4-00c04fd430c8 51 return "\x6b\xa7\xb8\x11\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8" 52} 53func uuid_ns_oid() -> *u8 { 54 // 6ba7b812-9dad-11d1-80b4-00c04fd430c8 55 return "\x6b\xa7\xb8\x12\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8" 56} 57func uuid_ns_x500() -> *u8 { 58 // 6ba7b814-9dad-11d1-80b4-00c04fd430c8 59 return "\x6b\xa7\xb8\x14\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8" 60} 61 62// Produce a 16-byte UUIDv5. namespace must be 16 bytes; name 63// is an arbitrary byte sequence. 64func uuid_v5_bytes(namespace: *u8, name: *u8, name_len: i64, 65 out: *u8) -> i64 { 66 // Concatenate namespace || name into a scratch buffer. 67 let buf_len: i64 = 16 + name_len 68 let buf: *u8 = sys_mmap(buf_len + 16) 69 var i: i64 = 0 70 while i < 16 { 71 buf[i] = namespace[i] 72 i = i + 1 73 } 74 i = 0 75 while i < name_len { 76 buf[16 + i] = name[i] 77 i = i + 1 78 } 79 80 // SHA-1 -> 20 bytes; we keep first 16. 81 let hash: *u8 = sys_mmap(32) 82 sha1(buf, buf_len, hash) 83 84 i = 0 85 while i < 16 { 86 out[i] = hash[i] 87 i = i + 1 88 } 89 90 // Stamp version (5) and variant (RFC 4122 = 10xx). 91 out[6] = (out[6] & 0x0F) | 0x50 92 out[8] = (out[8] & 0x3F) | 0x80 93 return 0 94} 95 96// Compile-only smoke. RFC 4122 Appendix B worked example: 97// uuidv5(DNS, "python.org") = 886313e1-3b8a-5372-9b90-0c9aee199e5d 98func main() -> i64 { 99 let out: *u8 = sys_mmap(32) 100 uuid_v5_bytes(uuid_ns_dns(), "python.org", 10, out) 101 102 // Check first byte (0x88), version nibble, variant nibble. 103 if out[0] != 0x88 { return 1 } 104 if out[1] != 0x63 { return 2 } 105 // version nibble is high half of out[6]. 106 if ((out[6] >> 4) & 0xF) != 5 { return 3 } 107 // variant is top two bits of out[8]: must be 10. 108 if ((out[8] >> 6) & 0x3) != 2 { return 4 } 109 110 // Determinism: same input -> same output. 111 let out2: *u8 = sys_mmap(32) 112 uuid_v5_bytes(uuid_ns_dns(), "python.org", 10, out2) 113 var i: i64 = 0 114 while i < 16 { 115 if out[i] != out2[i] { return 5 } 116 i = i + 1 117 } 118 119 // Different name -> different UUID. 120 let out3: *u8 = sys_mmap(32) 121 uuid_v5_bytes(uuid_ns_dns(), "ruby-lang.org", 13, out3) 122 var diff: i64 = 0 123 i = 0 124 while i < 16 { 125 if out[i] != out3[i] { diff = 1; break } 126 i = i + 1 127 } 128 if diff != 1 { return 6 } 129 return 0 130}