Specification · Draft v1.0 · 17AUG2026

UUID256

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.

128-bit UUIDf47ac10b-58cc-4372-a567-0e02b2c3d479
UUID2560001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f
256 bits / 32 bytes 250 random bits CSPRNG only, stateless no timestamp or machine id binary ≡ text ≡ numeric order birthday bound ≈ 2¹²⁵ ids 39 reference implementations

Introduction

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.

Generation

32 bytes from the OS CSPRNG, then two bytes are touched to stamp version (4) and variant (10). Nothing else changes.

Stateless

No clock, counter, or machine identity. Any number of generators — threads, processes, machines — run forever without coordinating.

Ordering

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.

Parsing

Accepts the 68-char hyphenated canonical form or the 64-char compact form, any letter case; strict mode also checks version/variant.

Storage

BYTEA / BINARY(32) / BLOB preferred; CHAR(68) where text is required.

Specification

The normative spec is included in full below — text format, binary layout, generation, parsing, storage, and test vectors.

Reference implementations

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.

Native & systems

C uuid256.c cc -O3 -pthread -o uuid256 uuid256.c && ./uuid256
C++ uuid256.cpp c++ -std=c++20 -O3 -o uuid256-cpp uuid256.cpp
Rust uuid256.rs rustc --edition 2021 -O -o uuid256-rs uuid256.rs
Zig uuid256.zig zig run -OReleaseFast uuid256.zig
Go uuid256.go go run uuid256.go
Swift uuid256.swift swift uuid256.swift -g

JVM

Java uuid256.java java uuid256.java
Kotlin uuid256.kt kotlinc uuid256.kt -include-runtime -d uuid256.jar
Scala uuid256.scala scala run uuid256.scala -- -g
Clojure uuid256.clj clojure -M uuid256.clj

.NET

C# uuid256.cs dotnet run uuid256.cs
F# uuid256.fsx dotnet fsi --optimize+ uuid256.fsx

Web & app

JavaScript uuid256.js node uuid256.js
TypeScript uuid256.ts node uuid256.ts
WebAssembly uuid256.wat + uuid256-wasm-host.js node uuid256-wasm-host.js hand-written WAT; Node host supplies CSPRNG bytes + I/O only
Dart uuid256.dart dart run uuid256.dart

Scripting

Python uuid256.py python3 uuid256.py
Ruby uuid256.rb ruby uuid256.rb
Perl uuid256.pl perl uuid256.pl
PHP uuid256.php php uuid256.php
Lua uuid256.lua lua uuid256.lua
Bash uuid256.sh ./uuid256.sh
PowerShell uuid256.ps1 pwsh uuid256.ps1

Data & numeric

R uuid256.R Rscript uuid256.R
Julia uuid256.jl julia uuid256.jl
Fortran uuid256.f90 gfortran -O2 -o uuid256-f90 uuid256.f90

Functional

Haskell uuid256.hs ghc -O2 -o uuid256-hs uuid256.hs
OCaml uuid256.ml ocaml uuid256.ml -g
Elixir uuid256.exs elixir uuid256.exs

Legacy

COBOL uuid256.cob cobc -x -O2 -o uuid256-cob uuid256.cob

Database

SQLite uuid256.sqlite.sql sqlite3 :memory: < uuid256.sqlite.sql
PostgreSQL uuid256.postgres.sql psql -f uuid256.postgres.sql
MySQL uuid256.mysql.sql mysql -N -B db < uuid256.mysql.sql
SQL Server uuid256.mssql.sql sqlcmd -b -S localhost -d tempdb -i uuid256.mssql.sql

Assembly & hardware

AArch64 uuid256.aarch64.s cc -o uuid256-asm uuid256.aarch64.s runs natively on Apple silicon
x86-64 uuid256.x86_64.S cc -arch x86_64 -o uuid256-x64 uuid256.x86_64.S macOS via Rosetta 2, or native on Linux
i386 uuid256.i386.s python3 emulate.py uuid256-i386 5 no native macOS target — run under emulate.py (Unicorn)
RISC-V 64 uuid256.riscv64.s python3 emulate.py uuid256-rv64 5 same emulate.py harness
Verilog uuid256.v + _tb.v iverilog -o uuid256.vvp uuid256.v uuid256_tb.v && vvp uuid256.vvp +selftest

Specification, in full

Draft v1.0 · 2026-08-17 · Binary layout, canonical text format, generation, parsing, storage, and comparison rules for a 256-bit random unique key.

StatusDraft v1.0
Date2026-08-17
ScopeBinary 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.

1. Purpose

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:

  • 250 bits of cryptographically random data (vs. 122 for UUIDv4), making collisions unreachable in practice at any generation rate, with no coordination, clocks, or node identifiers.
  • Nothing embedded, nothing leaked: no timestamp, sequence, or machine identity can be recovered from an id.
  • A text format that is visually kin to the standard UUID so operators immediately recognise it, yet cannot be mistaken for one.

2. Comparison with the standard 128-bit UUID

PropertyUUID (RFC 9562)UUID256 (this spec)
Size128 bits / 16 bytes256 bits / 32 bytes
Hex digits3264
Canonical text8-4-4-4-12 (5 groups)16-8-8-8-24 (5 groups)
Canonical length36 chars68 chars
Examplef47ac10b-58cc-4372-a567-0e02b2c3d4790001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f
Version digit1st hex digit of group 31st hex digit of group 3 (same position)
Variant digit1st hex digit of group 41st hex digit of group 4 (same position)
Contentv4: randomrandom
Random bitsv4: 122250

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.

3. Canonical text format

3.1 Definition

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.
  • Every other digit is random.
  • Total length: 68 characters (64 hex digits + 4 hyphens).
  • Hyphens are at 0-based string offsets 16, 25, 34, 43.

3.2 Rules

  1. Output MUST use lowercase hexadecimal digits (0-9, a-f).
  2. Output MUST include exactly the four hyphens at the positions above.
  3. Parsers MUST accept uppercase and mixed-case input and MUST normalise to lowercase.
  4. Parsers SHOULD accept the compact form (64 hex digits, no hyphens) as input; the compact form MUST NOT be emitted where the canonical form is expected.
  5. Braces {…}, urn: prefixes, whitespace, and any other decoration are NOT part of the format and MUST be rejected by strict parsers.

3.3 Grammar (ABNF)

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

3.4 Regular expressions

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.

3.5 Formats considered and rejected

LayoutLengthWhy not
16-8-8-8-24 (chosen)68Proportional 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)73Ten 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–74Same problems; also invites tooling to split it into two 128-bit values.
8-8-8-8-8-8-8-871Uniform but eight groups is harder to scan and no group has semantic meaning.
16-16-16-1667Groups too long to eyeball; loses the UUID silhouette.
64 hex, no hyphens64Compact but painful to read/compare by eye. Retained as the compact input form only.
Crockford base3252Shorter, but not hex, no visual kinship with UUIDs, and needs its own alphabet handling.
base64url43Shortest, but case-sensitive and its lexical order does not match numeric order. Suitable only as an opaque transport encoding (see §9).

4. Binary layout

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.

FieldBitsWidthBytesHex digitsText groupDescription
rand_a0–95960–110–231–2Cryptographically random.
ver96–99412 (high nibble)243Version. 0100 (4) for this layout.
rand_b100–1272812 (low nibble)–1525–313Cryptographically random.
var128–129216 (top 2 bits)32 (partial)4Variant. 10 — hex digit 32 is therefore 8, 9, a, or b.
rand_c130–25512616 (low 6 bits)–3132 (partial)–634–5Cryptographically 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.

4.1 Version and variant

  • Version (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.
  • Variant (var, 2 bits) is a family discriminator, mirroring the RFC 9562 convention. This document defines 10. Values 00, 01, 11 are reserved.
  • Both fields sit at the same relative positions as in the 128-bit UUID (first hex digit of group 3 and group 4), so a human can read "version 4, variant 8-b" straight from the text.

5. Generation

5.1 Algorithm

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

5.2 Requirements

  1. All 32 bytes MUST be drawn from a cryptographically secure random source (/dev/urandom, getrandom(2), crypto.getRandomValues, SecureRandom, os.urandom, etc.). Non-cryptographic PRNGs (Mersenne Twister, xorshift, rand(), Math.random) MUST NOT be used.
  2. Random bytes MUST NOT be reused across ids, derived from user input, or generated from a seed that could repeat (e.g. a forked process inheriting a userspace RNG state — implementations SHOULD prefer the OS entropy source directly, or reseed after fork()).
  3. Generators MUST NOT emit values with any version or variant other than those in §4.1.
  4. Because generation is stateless, any number of generators (threads, processes, machines) MAY run concurrently without coordination.

5.3 Uniqueness argument

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 generatedP(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¹²⁵.

6. Parsing and validation

A conforming parser:

  1. Trims nothing — surrounding whitespace is an error in strict mode (lenient mode MAY trim).
  2. Accepts exactly 68 characters with hyphens at offsets 16, 25, 34, 43, or exactly 64 characters with no hyphens.
  3. Accepts hex digits in either case; rejects any other character.
  4. Decodes to 32 bytes.
  5. In strict mode additionally checks 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.

7. Storage, comparison, and ordering

7.1 Ordering

All of the following orderings are identical by construction:

  • numeric order of the 256-bit unsigned integer,
  • byte-wise (memcmp) order of the 32-byte big-endian encoding,
  • byte-wise / code-point order of the canonical lowercase text.

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:

  • New keys land at uniformly random positions in a B-tree / clustered index (same characteristic as UUIDv4). For write-heavy tables where this matters, keep a separate created_at column for chronological queries, or use a non-clustered / hash index on the id.
  • Range scans over the id space are only useful for sharding/partitioning by prefix, which random ids do evenly.
SystemTypeNotes
PostgreSQLBYTEA (32 bytes)The native uuid type is 128-bit and cannot hold a UUID256. Add CHECK (octet_length(id) = 32).
MySQL / MariaDBBINARY(32)
SQLiteBLOB
SQL ServerBINARY(32)
Cassandra / ScyllaDBblob
Any RDBMS, text columnCHAR(68) / VARCHAR(68)Store canonical lowercase; use a binary/case-sensitive collation.
JSON / protobuf string / HTTP / logscanonical text68 chars.
protobuf bytes, msgpack bin, etc.32 raw bytesBig-endian as defined in §4.

Binary storage is preferred (32 vs 68 bytes, faster comparisons). Text storage is acceptable where readability or tooling matters.

7.3 Prefix use

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.

8. Special values

NameCanonical textMeaning
Nil0000000000000000-00000000-00000000-00000000-000000000000000000000000"No id" sentinel. Never generated.
Maxffffffffffffffff-ffffffff-ffffffff-ffffffff-ffffffffffffffffffffffffUpper 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.

9. Alternate encodings (non-normative)

Where the 68-character canonical form is too long (URLs, QR codes, tokens), a system MAY transport the same 32 bytes as:

EncodingLengthNotes
Compact hex64Case-insensitive; preserves sort order if lowercase.
Crockford base32 (no padding)52Case-insensitive, URL-safe, preserves sort order.
base64url (no padding)43Shortest; 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.

10. Security and privacy considerations

  • A UUID256 embeds no timestamp, counter, or machine identity; nothing about when, where, or how fast ids were generated can be recovered from the value.
  • With a CSPRNG, all 250 random bits are unpredictable, so an id cannot be guessed or enumerated. It is nonetheless an identifier, not a credential: authorisation MUST NOT rest on the secrecy of an id, because ids routinely appear in logs, URLs, and foreign keys. Use purpose-built tokens for secrets.
  • The security of the uniqueness guarantee is exactly the security of the random source. A weak or repeating RNG (§5.2) is the only realistic way to produce a collision.

11. Test vectors

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 text0001020304050607-08090a0b-4c0d0e0f-90111213-1415161718191a1b1c1d1e1f
Changed bytesbyte 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 textfffefdfcfbfaf9f8-f7f6f5f4-43f2f1f0-afeeedec-ebeae9e8e7e6e5e4e3e2e1e0
Changed bytesbyte 12: f3 → 43 (version); byte 16: ef → af (top bits 11 → 10)

Decoded fields of vector 1:

FieldValue
rand_a0x000102030405060708090a0b
ver4
rand_b0xc0d0e0f
var10 (hex digit 9)
rand_c0x101112131415161718191a1b1c1d1e1f (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

12. Summary of decisions

  • Width: 256 bits / 32 bytes / 64 hex digits.
  • Text format: 16-8-8-8-24, lowercase, 68 characters — the standard 8-4-4-4-12 with every group doubled.
  • Layout: purely random — 250 CSPRNG bits, with a version nibble (4) and variant bits (10) at the same relative positions as a standard UUID.
  • Generation: stateless; no clock, counter, or coordination.
  • Ordering: binary and text order agree but carry no meaning; index inserts are uniformly distributed (as with UUIDv4).
  • Storage: 32-byte binary preferred; CHAR(68) acceptable.