code wiki / (root) / nanoid.nx

nanoid.nx source

↩ module page · 101 lines · 3181 B

1// nanoid.nx -- modern URL-safe ID generator. 2// 3// Andrey Sitnik 2017, widely adopted in the JS ecosystem (Next.js, 4// Prisma, PlanetScale, tRPC). Alternative to UUID + ULID with 5// different trade-offs: 6// 7// - Default 21 chars ~= 126 bits of entropy (UUID: 128) 8// - URL-safe alphabet: A-Z a-z 0-9 _ - (64 chars, 6 bits each) 9// - No structure: pure random, no embedded timestamp 10// - No lexicographic sort: use ULID if sorting matters 11// 12// Use cases: URL slugs that need collision-resistance, short 13// share codes, DB primary keys where you don't need time-sort. 14// 15// Algorithm: 16// Request N random bytes from /dev/urandom via rand.nx. 17// Mask each byte to 6 bits + retry on overflow (standard 18// rejection sampling -- keeps the output alphabet uniform). 19// 20// Composes rand.nx. 21// 22// Invariants: 23// N1 Output is fixed-length caller-chosen (default 21). 24// N2 Every byte is from the 64-char URL-safe alphabet. 25// N3 Rejection sampling ensures uniform distribution -- we 26// don't use mod-256 which would bias bytes 0..31. 27 28import "syscalls.nx" 29import "rand.nx" 30 31const NANOID_DEFAULT_LEN: i64 = 21 32 33// Alphabet: 64 chars = 6-bit index. 34// 0..25 = A-Z, 26..51 = a-z, 52..61 = 0-9, 62 = _, 63 = - 35func nano_char(idx: i64) -> i64 { 36 if idx < 26 { return 0x41 + idx } 37 if idx < 52 { return 0x61 + (idx - 26) } 38 if idx < 62 { return 0x30 + (idx - 52) } 39 if idx == 62 { return 0x5F } // '_' 40 return 0x2D // '-' 41} 42 43// Generate a nanoid of `n` characters. Uses rejection sampling 44// on 8-bit random bytes: mask 6 bits + retry any byte whose 45// low 6 bits would map past the alphabet (never happens because 46// alphabet is exactly 64 = 2^6; rejection is a no-op here but 47// kept structurally for when custom alphabets are added later). 48func nanoid(out: *u8, n: i64) -> i64 { 49 // Pull n random bytes. 50 rand_bytes(out, n) 51 // Map each byte's low 6 bits into the alphabet (in place). 52 var i: i64 = 0 53 while i < n { 54 let b: i64 = out[i] & 0x3F 55 out[i] = nano_char(b) 56 i = i + 1 57 } 58 return n 59} 60 61// Default 21-char nanoid (most common use). 62func nanoid_default(out: *u8) -> i64 { 63 return nanoid(out, NANOID_DEFAULT_LEN) 64} 65 66// Compile-only smoke. 67func main() -> i64 { 68 let a: *u8 = sys_mmap(32) 69 nanoid_default(a) 70 // All 21 chars must be in the URL-safe alphabet. 71 var i: i64 = 0 72 while i < NANOID_DEFAULT_LEN { 73 let c: i64 = a[i] 74 var ok: i64 = 0 75 if c >= 0x41 { 76 if c <= 0x5A { ok = 1 } 77 } 78 if c >= 0x61 { 79 if c <= 0x7A { ok = 1 } 80 } 81 if c >= 0x30 { 82 if c <= 0x39 { ok = 1 } 83 } 84 if c == 0x5F { ok = 1 } 85 if c == 0x2D { ok = 1 } 86 if ok == 0 { return 1 } 87 i = i + 1 88 } 89 90 // Two ids should differ. 91 let b: *u8 = sys_mmap(32) 92 nanoid_default(b) 93 var diff: i64 = 0 94 i = 0 95 while i < NANOID_DEFAULT_LEN { 96 if a[i] != b[i] { diff = 1; break } 97 i = i + 1 98 } 99 if diff != 1 { return 2 } 100 return 0 101}