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.
- 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).
go get go.codycody31.dev/nano64import (
"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
}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)) // -1IDs 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()) // trueThe 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. |
| 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 | ✅ (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 |
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
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)
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
Compare(a, b Nano64) int- Compare two IDs (-1, 0, 1)Equals(other Nano64) bool- Check equality
Value() (driver.Value, error)- Implementsdriver.Valuerfor SQL storageScan(value interface{}) error- Implementssql.Scannerfor SQL retrieval
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
| 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
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.goBenchmark 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
Run:
go test -vAll 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
MIT License