Nano64 - 64‑bit Time‑Sortable Identifiers for Go
October 10, 2025 · View on GitHub
Nano64 is a lightweight library for generating time-sortable, globally unique IDs that offer the same practical guarantees as ULID or UUID in half the storage footprint; reducing index and I/O overhead while preserving cryptographic-grade randomness. Includes optional monotonic sequencing and AES-GCM encryption.
Note: This is a Go port of the original Nano64 TypeScript/JavaScript library by @only-cliches. All credit for the original concept, design, and implementation goes to the original author. This port aims to bring the same powerful, compact ID generation capabilities to the Go ecosystem.
Features
- Time‑sortable: IDs order by creation time automatically.
- Compact: 8 bytes / 16 hex characters.
- Deterministic format:
[63‥20]=timestamp,[19‥0]=random. - Cross‑database‑safe: Big‑endian bytes preserve order in SQLite, Postgres, MySQL, etc.
- AES-GCM encryption: Optional encryption masks the embedded creation date.
- Unsigned canonical form: Single, portable representation (0..2⁶⁴‑1).
Installation
go get go.codycody31.dev/nano64
Usage
Basic ID generation
import (
"fmt"
"go.codycody31.dev/nano64"
)
func main() {
id, err := nano64.GenerateDefault()
if err != nil {
panic(err)
}
fmt.Println(id.ToHex()) // 17‑char uppercase hex TIMESTAMP-RANDOM
// 199C01B6659-5861C
fmt.Println(id.ToBytes()) // [8]byte
// [25 156 1 182 101 149 134 28]
fmt.Println(id.GetTimestamp()) // ms since epoch
// 1759864645209
}
Monotonic generation
Ensures strictly increasing values even if created in the same millisecond.
a, err := nano64.GenerateMonotonicDefault()
if err != nil {
panic(err)
}
b, err := nano64.GenerateMonotonicDefault()
if err != nil {
panic(err)
}
fmt.Println(nano64.Compare(a, b)) // -1
AES‑GCM encryption
IDs can easily be encrypted and decrypted to mask their timestamp value from public view.
import "go.codycody31.dev/nano64"
// Create encryption key (32 bytes for AES-256)
key := make([]byte, 32)
if _, err := rand.Read(key); err != nil {
panic(err)
}
config, err := nano64.NewEncryptedIDConfig(key, nil, nil)
if err != nil {
panic(err)
}
// Generate and encrypt
wrapped, err := config.GenerateEncryptedNow()
if err != nil {
panic(err)
}
fmt.Println(wrapped.ID.ToHex()) // Unencrypted ID
// 199C01B66F8-CB911
fmt.Println(wrapped.ToEncryptedHex()) // 72‑char hex payload
// 2D5CEBF218C569DDE077C4C1F247C708063BAA93B4285CD67D53327EA4C374A64395CFF0
// Decrypt later
restored, err := config.FromEncryptedHex(wrapped.ToEncryptedHex())
if err != nil {
panic(err)
}
fmt.Println(restored.ID.Uint64Value() == wrapped.ID.Uint64Value()) // true
Database primary key storage
The Nano64 type implements database/sql/driver.Valuer and sql.Scanner interfaces for seamless database integration.
Store id.ToBytes() as an 8‑byte big‑endian binary value, or use the built-in SQL support:
import (
"database/sql"
"go.codycody31.dev/nano64"
)
type User struct {
ID nano64.Nano64
Name string
}
// Insert
id, _ := nano64.GenerateDefault()
_, err := db.Exec("INSERT INTO users (id, name) VALUES (?, ?)", id, "Alice")
// Query
var user User
err = db.QueryRow("SELECT id, name FROM users WHERE id = ?", id).Scan(&user.ID, &user.Name)
Database compatibility:
| DBMS | Column Type | Preserves Order | Notes |
|---|---|---|---|
| SQLite | BLOB (8 bytes) | ✅ | Lexicographic byte order matches unsigned big-endian. |
| PostgreSQL | BYTEA (8 bytes) | ✅ | PRIMARY KEY on BYTEA is fine. |
| MySQL 8+ | BINARY(8) | ✅ | Binary collation. |
| MariaDB | BINARY(8) | ✅ | Same as MySQL. |
| SQL Server | BINARY(8) | ✅ | Clustered index sorts by bytes. |
| Oracle | RAW(8) | ✅ | RAW compares bytewise. |
| CockroachDB | BYTES (8) | ✅ | Bytewise ordering. |
| DuckDB | BLOB (8) | ✅ | Bytewise ordering. |
Comparison with other identifiers
| Property | Nano64 | ULID | UUIDv4 | Snowflake ID |
|---|---|---|---|---|
| Bits total | 64 | 128 | 128 | 64 |
| Encoded timestamp bits | 44 | 48 | 0 | 41 |
| Random / entropy bits | 20 | 80 | 122 | 22 (per-node sequence) |
| Sortable by time | ✅ Yes (lexicographic & numeric) | ✅ Yes | ❌ No | ✅ Yes |
| Collision risk (1%) | ~145 IDs/ms (~0.04% at 145k/sec) | ~26M/ms | Practically none | None (central sequence) |
| Typical string length | 16 hex chars | 26 Crockford base32 | 36 hex+hyphens | 18–20 decimal digits |
| Encodes creation time | ✅ | ✅ | ❌ | ✅ |
| Can hide timestamp | ✅ via AES-GCM encryption | ⚠️ Not built-in | ✅ (no time field) | ❌ Not by design |
| Database sort order | ✅ Stable with big-endian BLOB | ✅ (lexical) | ❌ Random | ✅ Numeric |
| Cryptographic strength | 20-bit random, optional AES | 80-bit random | 122-bit random | None (deterministic) |
| Dependencies | None (crypto optional) | None | None | Central service or worker ID |
| Target use | Compact, sortable, optionally private IDs | Human-readable sortable IDs | Pure random identifiers | Distributed service IDs |
API Summary
Generation Functions
Generate(timestamp int64, rng RNG) (Nano64, error)- Creates a new ID with specified timestamp and RNGGenerateNow(rng RNG) (Nano64, error)- Creates an ID with current timestampGenerateDefault() (Nano64, error)- Creates an ID with current timestamp and default RNGGenerateMonotonic(timestamp int64, rng RNG) (Nano64, error)- Creates monotonic ID (strictly increasing)GenerateMonotonicNow(rng RNG) (Nano64, error)- Creates monotonic ID with current timestampGenerateMonotonicDefault() (Nano64, error)- Creates monotonic ID with current timestamp and default RNG
Parsing Functions
FromHex(hex string) (Nano64, error)- Parse from 16-char hex string (with or without dash)FromBytes(bytes []byte) (Nano64, error)- Parse from 8 big-endian bytesFromUint64(value uint64) Nano64- Create from uint64 valueNew(value uint64) Nano64- Create from uint64 value (alias)
ID Methods
ToHex() string- Returns 17-char uppercase hex (TIMESTAMP-RANDOM)ToBytes() []byte- Returns 8-byte big-endian encodingToDate() time.Time- Converts embedded timestamp to time.TimeGetTimestamp() int64- Extracts embedded millisecond timestampGetRandom() uint32- Extracts 20-bit random fieldUint64Value() uint64- Returns raw uint64 value
Comparison Functions
Compare(a, b Nano64) int- Compare two IDs (-1, 0, 1)Equals(other Nano64) bool- Check equality
Database Support
Value() (driver.Value, error)- Implementsdriver.Valuerfor SQL storageScan(value interface{}) error- Implementssql.Scannerfor SQL retrieval
Encrypted IDs
NewEncryptedIDConfig(key []byte, clock Clock, rng RNG) (*EncryptedIDConfig, error)- Create config with AES key (16, 24, or 32 bytes), optional clock and RNGconfig.GenerateEncrypted(timestamp int64) (*EncryptedNano64, error)- Generate and encrypt ID with specified timestampconfig.GenerateEncryptedNow() (*EncryptedNano64, error)- Generate and encrypt ID with current timestampconfig.Encrypt(id Nano64) (*EncryptedNano64, error)- Encrypt existing IDconfig.FromEncryptedHex(hex string) (*EncryptedNano64, error)- Decrypt from hexconfig.FromEncryptedBytes(bytes []byte) (*EncryptedNano64, error)- Decrypt from bytes
Design
| Bits | Field | Purpose | Range |
|---|---|---|---|
| 44 | Timestamp (ms) | Chronological order | 1970–2527 |
| 20 | Random | Collision avoidance | 1,048,576 patterns/ms |
Collision characteristics:
- Theoretical: ~1% collision probability at 145 IDs/millisecond
- Real-world sustained rate (145k IDs/sec): <0.05% collision rate
- High-speed burst (3.4M IDs/sec): ~0.18% collision rate
- Concurrent generation (10.6M IDs/sec): ~0.58% collision rate
Examples
The internal/examples directory contains comprehensive examples:
- Basic Usage - Simple ID generation and operations
- Monotonic Generation - Demonstrates strictly increasing IDs with per-millisecond sequencing
- Collision Resistance - Comprehensive collision resistance testing with real-world benchmarks
Run the collision resistance demonstration:
go run ./internal/examples/collision-resistance/main.go
Benchmark Results:
The collision resistance test performs four comprehensive scenarios:
- Single-threaded high-speed: 3.4M IDs/sec with 0.18% collisions
- Concurrent generation: 10.6M IDs/sec across 10 goroutines with 0.58% collisions
- Sustained safe rate: 145k IDs/sec over 10 seconds with <0.05% collisions
- Maximum throughput burst: 2.9M IDs/sec with 0.15% collisions
Tests
Run:
go test -v
All unit tests cover:
- Hex ↔ bytes conversions
- BigInt encoding
- Timestamp extraction and monotonic logic
- AES‑GCM encryption/decryption integrity
- Overflow edge cases
- Database driver.Valuer and sql.Scanner interfaces
License
MIT License