| Crates.io | nano64 |
| lib.rs | nano64 |
| version | 0.3.0 |
| created_at | 2025-10-10 01:42:46.431837+00 |
| updated_at | 2025-10-14 22:50:38.57225+00 |
| description | 64-bit Time-Sortable Identifiers for Rust |
| homepage | |
| repository | https://github.com/matthewoestreich/rs-nano64 |
| max_upload_size | |
| id | 1876412 |
| size | 85,621 |
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 Rust 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 Rust ecosystem. Also, a huge shout out to the Go port!
[63‥20]=timestamp, [19‥0]=random.cargo add nano64
use nano64::*;
fn main() -> Result<(), Nano64Error> {
let id = Nano64::generate_default()?;
println!("{}", id.to_hex()); // 17‑char uppercase hex TIMESTAMP-RANDOM
// 199CB26E5C1-706DF
println!("{:?}", id.to_bytes()); // [8]byte
// [25, 156, 178, 110, 92, 23, 6, 223]
println!("{}", id.get_timestamp()); // ms since epoch
// 1760049948097
Ok(())
}
Ensures strictly increasing values even if created in the same millisecond.
fn main() -> Result<(), Nano64Error> {
let a = Nano64::generate_monotonic_default()?;
let b = Nano64::generate_monotonic_default()?;
println!("{}", nano64::compare(&a, &b)); // -1
Ok(())
}
IDs can easily be encrypted and decrypted to mask their timestamp value from public view.
fn main() -> Result<(), Nano64Error> {
// Create 32-byte key (we use AES-256)
let key: [u8; 32] = [
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC,
0xFE, 0x0F, 0x1E, 0x2D, 0x3C, 0x4B, 0x5A, 0x69, 0x78, 0x87, 0x96, 0xA5, 0xB4, 0xC3, 0xD2,
0xE1, 0xF0,
];
let factory = Nano64::encrypted_factory(&key, None, None)?;
// Generate and encrypt
let wrapped = factory.generate_encrypted_now()?;
// or provide your own timestamp
// let wrapped = factory.generate_encrypted(your_timestamp)?;
println!("{}", wrapped.id.to_hex()); // Unencrypted ID
// 199CB349B6C-F84AC
println!("{}", wrapped.to_encrypted_hex()); // 72-char hex payload
// D8A385F53E9AC7E13C04CDBA88C52629ED2A3B31422BF474569BBF3E482B7CCCC1605309
// Decrypt later
let restored = factory.from_encrypted_hex(wrapped.to_encrypted_hex())?;
println!("{}", restored.id.u64_value() == wrapped.id.u64_value()); // true
Ok(())
}
| 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 |
Nano64::generate(timestamp: u64, rng: Option<RandomNumberGeneratorImpl>) -> Result<Nano64, Nano64Error>
Nano64::generate_now(rng: Option<RandomNumberGeneratorImpl>) -> Result<Nano64, Nano64Error>
Nano64::generate_default() -> Result<Nano64, Nano64Error>
Nano64::generate_monotonic(timestamp: u64, rng: Option<RandomNumberGeneratorImpl>) -> Result<Nano64, Nano64Error>
Nano64::generate_monotonic_now(rng: Option<RandomNumberGeneratorImpl>) -> Result<Nano64, Nano64Error>
Nano64::generate_monotonic_default() -> Result<Nano64, Nano64Error>
Nano64::from_str(hex_str: &str) -> Result<Nano64, Nano64Error>;
// parse &str or String
&str.parse::<Nano64>() -> Result<Nano64, Nano64Error>;
String.parse::<Nano64>() -> Result<Nano64, Nano64Error>;
// try_from &str or String
Nano64::try_from(str: &str) -> Result<Nano64, Nano64Error>;
Nano64::try_from(str: String) -> Result<Nano64, Nano64Error>;
Nano64::from(bytes: [u8; 8]) -> Nano64
Nano64::from(value: u64) -> Nano64
new(value: u64) -> Nano64
to_hex() -> String - Returns 17-char uppercase hex (TIMESTAMP-RANDOM)to_bytes() -> [u8; 8] - Returns 8-byte big-endian encodingto_date() -> SystemTime - Converts embedded timestamp to SystemTimeget_timestamp() -> u64 - Extracts embedded millisecond timestampget_random() -> u32 - Extracts 20-bit random fieldu64_value() -> u64 - Returns raw u64 valuenano64::compare(a: &Nano64, b: &Nano64) -> i64
<Nano64>.equals(other &Nano64) -> bool
In-progress!
encrypted_factory(key: &[u8], clock: Option<Clock>, rng: Option<RandomNumberGeneratorImpl>) -> Result<Nano64EncryptionFactory, Nano64Error>
factory.generate_encrypted(timestamp: u64) -> Result<Nano64Encrypted, Nano64Error>
factory.encrypt(id: Nano64) -> Result<Nano64Encrypted, Nano64Error>
factory.from_encrypted_hex(hex: String) -> Result<Nano64Encrypted, Nano64Error>
factory.from_encrypted_bytes(bytes: &[u8]) -> Result<Nano64Encrypted, Nano64Error>
| Bits | Field | Purpose | Range |
|---|---|---|---|
| 44 | Timestamp (ms) | Chronological order | 1970–2527 |
| 20 | Random | Collision avoidance | 1,048,576 patterns/ms |
Run the collision resistance demonstration:
cargo run --release
Benchmark Results:
The collision resistance test performs four comprehensive scenarios:
Run:
cargo test
All unit tests cover:
MIT License