Hex.pm CI

Unique identifier generation for Erlang/OTP 27+: RFC 4122 and RFC 9562 UUIDs, plus sortable nuid identifiers.

Getting started

%% rebar.config
{deps, [nuid]}.
1> nuid:uuid4().
<<"37a9e737-f680-44a9-b83d-a517ec758b75">>
2> nuid:uuid7().
<<"018b3d7a-9f9a-7577-adb2-08761e3d87f7">>
3> nuid:nuid2().
<<"OHtpP-----Fkn3F6JaT5Kxnm_NAiDzFgGMzc">>
4> nuid:uuid7_info(nuid:uuid7()).
{{2023,10,17},{11,52,8}}

Features

  • RFC 4122 UUIDs versions 1, 3 (MD5), 4, and 5 (SHA-1) (RFC 4122)
  • RFC 9562 UUIDs versions 6, 7, and 8 (RFC 9562)
  • Nil and max UUIDs
  • nuid1 and nuid2 sortable identifiers with 128 bits of cryptographically strong randomness
  • Info recovery creation time and originating node from generated identifiers
  • No dependencies only OTP kernel, stdlib, and crypto

API

FunctionDescription
nuid:uuid1/0RFC 4122 UUID v1
nuid:uuid3/2RFC 4122 UUID v3 (MD5, name-based)
nuid:uuid4/0RFC 4122 UUID v4 (random)
nuid:uuid5/2RFC 4122 UUID v5 (SHA-1, name-based)
nuid:uuid6/0RFC 9562 UUID v6 (time-ordered)
nuid:uuid7/0RFC 9562 UUID v7 (time-ordered)
nuid:uuid8/1, nuid:uuid8/3RFC 9562 UUID v8 (vendor-specific)
nuid:nil_uuid/0RFC 4122 nil UUID
nuid:max_uuid/0RFC 9562 max UUID
nuid:nuid1/0nuid1 identifier
nuid:nuid2/0nuid2 identifier
nuid:uuid1_info/1, nuid:uuid6_info/1Generation time, node, and counter
nuid:uuid7_info/1, nuid:nuid1_info/1Generation time
nuid:nuid2_info/1Generation time and node

The nuid identifiers

We have historically used UUID v1 (RFC 4122) and later UUID v6 (RFC 9562), preferring v6 because it can be ordered by creation time.

A later security review required identifiers with at least as many cryptographically strong random bits as UUID v4 (122). The requirements were:

  • Orderable by creation time.
  • At least 122 bits of cryptographically strong randomness.

With, optionally:

  • Uniqueness (or at least a low collision probability).
  • Information about where the identifier was created.

The two de facto standards we looked at, ulid and ksuid, do not meet the randomness requirement, so we defined nuid2.

nuid2 properties:

  • Lexicographically sortable.
  • Sixteen cryptographically strong random bytes (128 bits).
  • Unique (or at least a low collision probability).
  • Carries 3 bytes reserved for origin information.
  • No longer than a UUID (36 characters).
  • URL-safe.
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    POSIX time in seconds                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Unique sortable integer                    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|        Origin information (node)                |             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+             +
|                                                               |
+                                                               +
|          Cryptographically strong random bits (128)           |
+                                                               +
|                                                               |
+                                                 +-+-+-+-+-+-+-+
|                                                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

              Figure 1: nuid2 field and bit layout

We encode this in what we call base64': a URL-safe, sortable base64 representation. Its alphabet, in order:

-, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
_,
a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z

It is the Erlang/OTP base64 module reordered to preserve byte ordering.

nuid1 is a lighter identifier with these properties:

  • Lexicographically sortable. It can replace UUID v6: every nuid1 generated after a UUID v6 sorts after it.
  • Sixteen cryptographically strong random bytes (128 bits).
  • Unique (or at least a low collision probability).
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    Hex unique time in us  |-|       base64' 16 random bytes             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

              Figure 2: nuid1 field and byte layout

(*) the dash at byte 14 is ASCII 45

Benchmarks

Run a rebar3 shell using the bench profile:

rebar3 as bench shell
1> nuid_bench:bench().

This benchmark compares the different identifiers generated by nuid.

nuid1 creation time: 1.83 (us)
nuid2 creation time: 1.55 (us)
uuid1 creation time: 0.92 (us)
uuid4 creation time: 1.69 (us)
uuid6 creation time: 0.91 (us)
uuid7 creation time: 1.82 (us)

Documentation

nuid on HexDocs

License

Apache License 2.0