Specification · Draft v1.0 · 17AUG2026
A 256-bit (32-byte), purely random unique identifier, formatted 16-8-8-8-24 — the standard UUID's 8-4-4-4-12 with every group doubled.
The 256-bit analogue of UUIDv4 — same idea, twice the random bits, same visual shape.
UUID256 is a fixed-size, globally unique, purely random identifier meant for use as a primary or unique key in databases, message systems, and APIs. It carries 250 bits of cryptographically random data (versus 122 for UUIDv4), so collisions are unreachable in practice at any generation rate, with no coordination, clocks, or node identifiers required. Nothing about when, where, or how fast an id was generated can be recovered from it — there is nothing embedded to leak.
The text format is deliberately kin to the standard UUID: every field of 8-4-4-4-12 is doubled to 16-8-8-8-24, so the version digit still opens the third group and the variant digit still opens the fourth — exactly where a UUID reader already expects them — while the extra length makes it impossible to mistake for a real 128-bit UUID.
32 bytes from the OS CSPRNG, then two bytes are touched to stamp version (4) and variant (10). Nothing else changes.
No clock, counter, or machine identity. Any number of generators — threads, processes, machines — run forever without coordinating.
Numeric order of the 256-bit value, byte order of the encoding, and code-point order of the canonical text are identical — but carry no meaning, since the value is uniform random.
Accepts the 68-char hyphenated canonical form or the 64-char compact form, any letter case; strict mode also checks version/variant.
BYTEA / BINARY(32) / BLOB preferred; CHAR(68) where text is required.
The normative spec is included in full below — text format, binary layout, generation, parsing, storage, and test vectors.
All 39 implement the same API — generate, to_canonical, parse (strict/lenient), is_strict — and self-check against the spec's test vectors on start-up. Sources live flat in reference/; only sources are kept there. Build/run commands assume you're inside that directory.
Draft v1.0 · 2026-08-17 · Binary layout, canonical text format, generation, parsing, storage, and comparison rules for a 256-bit random unique key.
| Status | Draft v1.0 |
| Date | 2026-08-17 |
| Scope | Binary layout, canonical text format, generation, parsing, storage, and comparison rules for a 256-bit random unique key |
Terminology. The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described in RFC 2119.
Size note. UUID256 is 256 bits = 32 bytes = 64 hexadecimal digits. This is exactly double the standard 128-bit UUID (16 bytes, 32 hex digits). Throughout, "UUID" without a qualifier means the 128-bit RFC 9562 UUID.
UUID256 is a fixed-size, globally unique, purely random identifier intended for use as a primary/unique key in databases, message systems, and APIs. It is the 256-bit analogue of UUIDv4:
| Property | UUID (RFC 9562) | UUID256 (this spec) |
|---|---|---|
| Size | 128 bits / 16 bytes | 256 bits / 32 bytes |
| Hex digits | 32 | 64 |
| Canonical text | 8-4-4-4-12 (5 groups) | 16-8-8-8-24 (5 groups) |
| Canonical length | 36 chars | 68 chars |
| Example | f47ac10b-58cc-4372-a567-0e02b2c3d479 | 0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f |
| Version digit | 1st hex digit of group 3 | 1st hex digit of group 3 (same position) |
| Variant digit | 1st hex digit of group 4 | 1st hex digit of group 4 (same position) |
| Content | v4: random | random |
| Random bits | v4: 122 | 250 |
The design rule is simple: every field of 8-4-4-4-12 is doubled, giving 16-8-8-8-24. This keeps the same five-group silhouette, the same relative field proportions, and keeps the version and variant digits at the same relative positions ("first character of the third group" / "first character of the fourth group") that people already know from standard UUIDs.
A UUID256 in canonical text form is the 64 hexadecimal digits of the 256-bit value (most-significant nibble first), split into five hyphen-separated groups of 16, 8, 8, 8, and 24 digits:
xxxxxxxxxxxxxxxx-xxxxxxxx-Vxxxxxxx-Rxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx |<---- 16 ---->| |<- 8 ->| |<- 8 ->| |<- 8 ->| |<-------- 24 -------->| group 1 group 2 group 3 group 4 group 5 bits 0–63 bits 64–95 bits 96–127 bits 128–159 bits 160–255
V = version digit (see §4), always 4 for this specification.R = variant digit (see §4), always one of 8, 9, a, b.0-9, a-f).{…}, urn: prefixes, whitespace, and any other decoration are NOT part of the format and MUST be rejected by strict parsers.uuid256 = group1 "-" group2 "-" group3 "-" group4 "-" group5
group1 = 16HEXDIG
group2 = 8HEXDIG
group3 = 8HEXDIG ; first digit is the version (4)
group4 = 8HEXDIG ; first digit is the variant (8/9/a/b)
group5 = 24HEXDIG
HEXDIG = DIGIT / "a" / "b" / "c" / "d" / "e" / "f" ; lowercase on output;
; A-F also accepted on input
compact = 64HEXDIG ; input-only alternate form
Structural (any version/variant):
^[0-9a-f]{16}-[0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{24}$
Strict — this specification's version (4) and variant (8–b):
^[0-9a-f]{16}-[0-9a-f]{8}-4[0-9a-f]{7}-[89ab][0-9a-f]{7}-[0-9a-f]{24}$
Add the i flag (or use [0-9a-fA-F]) when validating input; never when validating output.
| Layout | Length | Why not |
|---|---|---|
16-8-8-8-24 (chosen) | 68 | Proportional double of 8-4-4-4-12; version/variant keep their familiar positions; 5 groups scan easily. |
8-4-4-4-12-8-4-4-4-12 (two UUIDs) | 73 | Ten groups; reads as "two UUIDs glued together"; the second half would carry a meaningless fake version/variant. |
8-4-4-4-12 + separator + 8-4-4-4-12 (e.g. …::…) | 73–74 | Same problems; also invites tooling to split it into two 128-bit values. |
8-8-8-8-8-8-8-8 | 71 | Uniform but eight groups is harder to scan and no group has semantic meaning. |
16-16-16-16 | 67 | Groups too long to eyeball; loses the UUID silhouette. |
| 64 hex, no hyphens | 64 | Compact but painful to read/compare by eye. Retained as the compact input form only. |
| Crockford base32 | 52 | Shorter, but not hex, no visual kinship with UUIDs, and needs its own alphabet handling. |
| base64url | 43 | Shortest, but case-sensitive and its lexical order does not match numeric order. Suitable only as an opaque transport encoding (see §9). |
The value is a 256-bit unsigned integer, serialised big-endian (network byte order): byte 0 is the most significant byte and corresponds to hex digits 0–1 of the text form. Bit 0 is the most significant bit.
| Field | Bits | Width | Bytes | Hex digits | Text group | Description |
|---|---|---|---|---|---|---|
rand_a | 0–95 | 96 | 0–11 | 0–23 | 1–2 | Cryptographically random. |
ver | 96–99 | 4 | 12 (high nibble) | 24 | 3 | Version. 0100 (4) for this layout. |
rand_b | 100–127 | 28 | 12 (low nibble)–15 | 25–31 | 3 | Cryptographically random. |
var | 128–129 | 2 | 16 (top 2 bits) | 32 (partial) | 4 | Variant. 10 — hex digit 32 is therefore 8, 9, a, or b. |
rand_c | 130–255 | 126 | 16 (low 6 bits)–31 | 32 (partial)–63 | 4–5 | Cryptographically random. |
Totals: 4 version + 2 variant + 250 random = 256 bits.
byte: 0 1 2 3 4 5 6 7 8 9 10 11 |12 13 14 15 |16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
[--------------- rand_a ---------------] [V][ rand_b ] [R][------------------- rand_c -------------------]
text: group 1 (16 hex) group 2 (8) group 3 (8) group 4 (8) group 5 (24)
Only two bytes are ever touched after drawing randomness: the high nibble of byte 12 is forced to 4, and the top two bits of byte 16 are forced to 10.
ver, 4 bits) identifies the field layout. This document defines version 4 — random. The value was chosen to mirror UUIDv4's meaning so the digit reads the same way in both formats. All other values are reserved for future layouts and MUST NOT be emitted.var, 2 bits) is a family discriminator, mirroring the RFC 9562 convention. This document defines 10. Values 00, 01, 11 are reserved.Generation is stateless: no clock, counter, node id, or coordination is required.
function generate():
b = 32 bytes from a CSPRNG
b[12] = (b[12] & 0x0F) | 0x40 # ver = 4 → hex digit 24 is '4'
b[16] = (b[16] & 0x3F) | 0x80 # var = 10 → hex digit 32 is 8/9/a/b
return b
/dev/urandom, getrandom(2), crypto.getRandomValues, SecureRandom, os.urandom, etc.). Non-cryptographic PRNGs (Mersenne Twister, xorshift, rand(), Math.random) MUST NOT be used.fork()).Every id carries 250 independent, uniformly random bits. For n ids generated anywhere, ever, the probability of at least one collision is ≈ n² / 2²⁵¹.
| Ids generated | P(any collision) |
|---|---|
| 2⁶⁴ (≈1.8 × 10¹⁹) | ≈ 2⁻¹²³ |
| 2⁸⁰ | ≈ 2⁻⁹¹ |
| 2¹⁰⁰ | ≈ 2⁻⁵¹ |
| 2¹²⁵ | ≈ 50 % (birthday bound) |
For comparison, UUIDv4 has 122 random bits: 2⁶⁴ ids give ≈ 2⁻⁵ chance of a collision, and the 50 % birthday bound is at ≈ 2⁶¹ ids. UUID256 moves the birthday bound to ≈ 2¹²⁵.
A conforming parser:
ver == 4 and var == 10; in lenient mode accepts any version/variant so that future layouts and the special values (§8) can be transported.Errors MUST distinguish at least: wrong length, hyphen at wrong position, invalid character, unsupported version/variant.
All of the following orderings are identical by construction:
memcmp) order of the 32-byte big-endian encoding,Uppercase or mixed-case text would break the text-order property, which is why lowercase is mandatory on output.
Because the value is uniformly random, ordering carries no meaning: it is not creation order and reveals nothing. Consequently:
created_at column for chronological queries, or use a non-clustered / hash index on the id.| System | Type | Notes |
|---|---|---|
| PostgreSQL | BYTEA (32 bytes) | The native uuid type is 128-bit and cannot hold a UUID256. Add CHECK (octet_length(id) = 32). |
| MySQL / MariaDB | BINARY(32) | |
| SQLite | BLOB | |
| SQL Server | BINARY(32) | |
| Cassandra / ScyllaDB | blob | |
| Any RDBMS, text column | CHAR(68) / VARCHAR(68) | Store canonical lowercase; use a binary/case-sensitive collation. |
JSON / protobuf string / HTTP / logs | canonical text | 68 chars. |
protobuf bytes, msgpack bin, etc. | 32 raw bytes | Big-endian as defined in §4. |
Binary storage is preferred (32 vs 68 bytes, faster comparisons). Text storage is acceptable where readability or tooling matters.
Any fixed-length prefix of a UUID256 (e.g. the first 8 hex digits) is itself uniformly random and MAY be used for sharding, bucketing, or as a short display handle — with the usual collision math for the shortened width. Prefixes are never unique on their own and MUST NOT be used as keys.
| Name | Canonical text | Meaning |
|---|---|---|
| Nil | 0000000000000000-00000000-00000000-00000000-000000000000000000000000 | "No id" sentinel. Never generated. |
| Max | ffffffffffffffff-ffffffff-ffffffff-ffffffff-ffffffffffffffffffffffff | Upper bound sentinel (e.g. range scans). Never generated. |
Both are structurally valid, fail strict version/variant checks (by design), and MUST NOT be used as real keys.
Where the 68-character canonical form is too long (URLs, QR codes, tokens), a system MAY transport the same 32 bytes as:
| Encoding | Length | Notes |
|---|---|---|
| Compact hex | 64 | Case-insensitive; preserves sort order if lowercase. |
| Crockford base32 (no padding) | 52 | Case-insensitive, URL-safe, preserves sort order. |
| base64url (no padding) | 43 | Shortest; case-sensitive; does not preserve sort order. |
These are transport encodings only. Interfaces MUST document which encoding they accept, and the canonical form is always the interchange default.
Generated with the algorithm in §5.1 from fixed "random" input, showing exactly which bits change (byte 12 high nibble → 4, byte 16 top two bits → 10).
Vector 1 — input bytes 00 01 02 … 1f:
| Input (hex) | 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f |
| Output (hex) | 000102030405060708090a0b4c0d0e0f901112131415161718191a1b1c1d1e1f |
| Canonical text | 0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f |
| Changed bytes | byte 12: 0c → 4c (version); byte 16: 10 → 90 (variant 10 + low bits 010000) |
Vector 2 — input bytes ff fe fd … e0:
| Input (hex) | fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0 |
| Output (hex) | fffefdfcfbfaf9f8f7f6f5f443f2f1f0afeeedecebeae9e8e7e6e5e4e3e2e1e0 |
| Canonical text | fffefdfcfbfaf9f8-f7f6f5f4-43f2f1f0-afeeedec-ebeae9e8e7e6e5e4e3e2e1e0 |
| Changed bytes | byte 12: f3 → 43 (version); byte 16: ef → af (top bits 11 → 10) |
Decoded fields of vector 1:
| Field | Value |
|---|---|
rand_a | 0x000102030405060708090a0b |
ver | 4 |
rand_b | 0xc0d0e0f |
var | 10 (hex digit 9) |
rand_c | 0x101112131415161718191a1b1c1d1e1f (byte 16 = 0x90 with the top two bits cleared → 0x10) |
Live examples from a real generator (note the 4 at the head of group 3 and 8–b at the head of group 4; everything else is random):
c7959992fb0cd8bd-d05c7030-40de491e-88ea97ac-829bb01dfcb89f76bd13b06c 765122f3f03e9ab3-80d6c24e-4828ab37-82cd399b-1f9b8096377d81129215b4a4 683062f8f59f7c20-c97a1d85-40d83331-881b54e5-02d5666e7e1d3e06dca57453
16-8-8-8-24, lowercase, 68 characters — the standard 8-4-4-4-12 with every group doubled.4) and variant bits (10) at the same relative positions as a standard UUID.CHAR(68) acceptable.