Reversible UUID v4/v7 transformation using SipHash-2-4
β‘ Ultra-lightweight β’ π Zero dependencies β’ π¦ Just 1.68KB gzipped
UUIDv47 is a revolutionary approach to UUID management that allows you to reversibly transform UUIDs between v7 (time-ordered) and v4 (random-appearing) formats. This enables you to:
- π Hide time-ordering from external APIs while maintaining internal chronological benefits
- π Present v4 facades to clients while storing efficient v7 UUIDs internally
- π Cryptographically secure transformations using SipHash-2-4 algorithm
- β‘ High performance with >10,000 operations per second
- π Perfect reversibility - no data loss in transformations
- π¦ Tiny footprint - only 1.68KB gzipped with zero dependencies
UUIDv47 is a lightweight library at just 3.75KB (1.68KB gzipped) that provides enterprise-grade UUID transformation capabilities with zero dependencies.
- Installation
- Quick Start
- Core Concepts
- API Reference
- Usage Examples
- Performance
- Security
- Testing
- Contributing
# Using Bun (recommended)
bun add @usex/uuidv47
# Using npm
npm install @usex/uuidv47
# Using yarn
yarn add @usex/uuidv47
# Using pnpm
pnpm add @usex/uuidv47
- Node.js: 16.0.0+
- TypeScript: 5.0+ (for TypeScript projects)
- Runtime: Works with Node.js, Bun, Deno, and modern browsers
import { UUIDv47 } from '@usex/uuidv47';
// 1. Generate a secure transformation key
const key = UUIDv47.generateRandomKey();
// 2. Parse your UUIDv7 (from your UUID generator)
const originalV7 = UUIDv47.parseUUID('018f4e7c-3c4a-7000-8000-123456789abc');
// 3. Transform to v4 facade (for external APIs)
const v4Facade = UUIDv47.encodeV4Facade(originalV7, key);
console.log(UUIDv47.formatUUID(v4Facade));
// Output: "a1b2c3d4-e5f6-4789-9abc-def012345678" (appears random)
// 4. Transform back to original v7 (for internal use)
const decodedV7 = UUIDv47.decodeV4Facade(v4Facade, key);
console.log(UUIDv47.formatUUID(decodedV7));
// Output: "018f4e7c-3c4a-7000-8000-123456789abc" (original restored)
UUIDv7 provides excellent time-ordering properties but reveals creation timestamps to anyone who can parse them. UUIDv4 appears random but lacks useful ordering. UUIDv47 gives you both:
// Internal storage: UUIDv7 (time-ordered, efficient)
const internalId = "018f4e7c-3c4a-7000-8000-123456789abc"; // Created at specific timestamp
// External API: UUIDv4 facade (random-appearing, private)
const publicId = "a1b2c3d4-e5f6-4789-9abc-def012345678"; // Hides creation time
// They're the SAME identifier, just different representations!
- Timestamp Encryption: The 48-bit timestamp in UUIDv7 is XORed with a SipHash-2-4 derived mask
- Random Bits Preserved: The random portion remains unchanged, maintaining uniqueness
- Version Swap: Version bits are changed between 4 and 7 during transformation
- Perfect Reversibility: Using the same key, transformations are completely reversible
- SipHash-2-4: Cryptographically secure hash function for generating encryption masks
- 48-bit Timestamp: The time component that gets encrypted/decrypted
- Transformation Key: 128-bit key (two 64-bit values) for secure transformations
- UUID128: Buffer representation of 16-byte UUIDs for efficient processing
// Transform UUIDv7 to UUIDv4 facade
static encodeV4Facade(uuidV7: UUID128, key: UUIDv47Key): UUID128
// Transform UUIDv4 facade back to UUIDv7
static decodeV4Facade(uuidV4Facade: UUID128, key: UUIDv47Key): UUID128
// Generate cryptographically secure random key
static generateRandomKey(): UUIDv47Key
// Create key from two 64-bit bigint values
static createKey(k0: bigint, k1: bigint): UUIDv47Key
// Create key from 16-byte buffer (for loading from storage)
static createKeyFromBuffer(keyBuffer: Buffer): UUIDv47Key
// Parse UUID string to Buffer
static parseUUID(uuidString: string): UUID128
// Format UUID Buffer to string
static formatUUID(uuid: UUID128): string
// Get UUID version (4 or 7)
static getUUIDVersion(uuid: UUID128): UUIDVersion
// 128-bit UUID as Buffer
type UUID128 = Buffer;
// SipHash key structure
interface UUIDv47Key {
readonly k0: bigint; // First 64-bit key component
readonly k1: bigint; // Second 64-bit key component
}
// UUID version enumeration
enum UUIDVersion {
V4 = 4,
V7 = 7
}
import { UUIDv47 } from '@usex/uuidv47';
const key = UUIDv47.generateRandomKey();
const v7UUID = UUIDv47.parseUUID("018f4e7c-3c4a-7000-8000-123456789abc");
console.log(`Original UUIDv7: ${UUIDv47.formatUUID(v7UUID)}`);
console.log(`Version: ${UUIDv47.getUUIDVersion(v7UUID)}`); // 7
const v4Facade = UUIDv47.encodeV4Facade(v7UUID, key);
console.log(`UUIDv4 Facade: ${UUIDv47.formatUUID(v4Facade)}`);
console.log(`Version: ${UUIDv47.getUUIDVersion(v4Facade)}`); // 4
const decodedV7 = UUIDv47.decodeV4Facade(v4Facade, key);
console.log(`Decoded UUIDv7: ${UUIDv47.formatUUID(decodedV7)}`);
console.log(`Matches original: ${decodedV7.equals(v7UUID)}`); // true
class UUIDTransformationService {
private readonly key: UUIDv47Key;
constructor(keyMaterial?: Buffer) {
this.key = keyMaterial
? UUIDv47.createKeyFromBuffer(keyMaterial)
: UUIDv47.generateRandomKey();
}
// For external APIs - hide time ordering
toPublicId(internalV7: string): string {
const uuid = UUIDv47.parseUUID(internalV7);
const facade = UUIDv47.encodeV4Facade(uuid, this.key);
return UUIDv47.formatUUID(facade);
}
// For internal processing - restore time ordering
toInternalId(publicV4: string): string {
const uuid = UUIDv47.parseUUID(publicV4);
const original = UUIDv47.decodeV4Facade(uuid, this.key);
return UUIDv47.formatUUID(original);
}
}
// Usage
const service = new UUIDTransformationService();
const internalId = "018f4e7c-3c4a-7000-8000-123456789abc";
const publicId = service.toPublicId(internalId);
const recoveredId = service.toInternalId(publicId);
console.log({ internalId, publicId, recoveredId });
// {
// internalId: "018f4e7c-3c4a-7000-8000-123456789abc",
// publicId: "a1b2c3d4-e5f6-4789-9abc-def012345678",
// recoveredId: "018f4e7c-3c4a-7000-8000-123456789abc"
// }
class MultiTenantUUIDService {
private readonly tenantKeys = new Map<string, UUIDv47Key>();
setTenantKey(tenantId: string, keyMaterial: Buffer): void {
this.tenantKeys.set(tenantId, UUIDv47.createKeyFromBuffer(keyMaterial));
}
encodeForTenant(tenantId: string, v7UUID: string): string {
const key = this.tenantKeys.get(tenantId);
if (!key) throw new Error(`No key found for tenant: ${tenantId}`);
const uuid = UUIDv47.parseUUID(v7UUID);
const facade = UUIDv47.encodeV4Facade(uuid, key);
return UUIDv47.formatUUID(facade);
}
decodeForTenant(tenantId: string, v4Facade: string): string {
const key = this.tenantKeys.get(tenantId);
if (!key) throw new Error(`No key found for tenant: ${tenantId}`);
const uuid = UUIDv47.parseUUID(v4Facade);
const original = UUIDv47.decodeV4Facade(uuid, key);
return UUIDv47.formatUUID(original);
}
}
// Process 1000 UUIDs efficiently
const key = UUIDv47.generateRandomKey();
const v7UUIDs = generateV7UUIDs(1000); // Your UUID generation
console.time('Batch Encode');
const facades = v7UUIDs.map(uuid => UUIDv47.encodeV4Facade(uuid, key));
console.timeEnd('Batch Encode'); // ~10ms for 1000 UUIDs
console.time('Batch Decode');
const decoded = facades.map(facade => UUIDv47.decodeV4Facade(facade, key));
console.timeEnd('Batch Decode'); // ~10ms for 1000 UUIDs
// Verify all transformations are perfect
const allMatch = decoded.every((uuid, i) => uuid.equals(v7UUIDs[i]));
console.log(`All roundtrips successful: ${allMatch}`); // true
const key = UUIDv47.generateRandomKey();
try {
// This will throw - can only encode v7 UUIDs
const v4UUID = UUIDv47.parseUUID("a1b2c3d4-e5f6-4000-8000-123456789abc");
UUIDv47.encodeV4Facade(v4UUID, key);
} catch (error) {
console.log(error.message); // "Input UUID must be version 7"
}
try {
// This will throw - can only decode v4 UUIDs
const v7UUID = UUIDv47.parseUUID("018f4e7c-3c4a-7000-8000-123456789abc");
UUIDv47.decodeV4Facade(v7UUID, key);
} catch (error) {
console.log(error.message); // "Input UUID must be version 4"
}
try {
// This will throw - invalid UUID format
UUIDv47.parseUUID("invalid-uuid-string");
} catch (error) {
console.log(error.message); // "Invalid UUID string length: expected 36, got 19"
}
UUIDv47 is optimized for production use with exceptional performance characteristics based on comprehensive benchmarking:
Operation | Performance | Notes |
---|---|---|
Encode v7βv4 | ~149,000 ops/sec | Single UUID transformation |
Decode v4βv7 | ~150,000 ops/sec | Single UUID transformation |
Roundtrip (encode+decode) | ~75,000 ops/sec | Full transformation cycle |
Batch Encode (100 UUIDs) | ~1,500 ops/sec | Batch processing throughput |
Batch Decode (100 UUIDs) | ~1,500 ops/sec | Batch processing throughput |
Operation | Performance | Notes |
---|---|---|
Create from BigInts | ~16.4M ops/sec | Ultra-fast key creation |
Create from Buffer | ~10M ops/sec | Buffer-based key creation |
Generate Random Key | ~685,000 ops/sec | Cryptographically secure |
Batch Key Generation (100) | ~7,300 ops/sec | Bulk key operations |
Operation | Performance | Notes |
---|---|---|
Parse UUID String | ~5.7M ops/sec | String to Buffer conversion |
Format UUID Buffer | ~2.7M ops/sec | Buffer to String conversion |
Get UUID Version | ~16.6M ops/sec | Version extraction |
Parse-Format Roundtrip | ~2M ops/sec | Full string processing cycle |
UUID Validation Workflow | ~5.5M ops/sec | Parse + version check |
Scenario | Performance | Notes |
---|---|---|
Service Pattern (Public) | ~137,000 ops/sec | Single transformation to public ID |
Service Pattern (Internal) | ~69,000 ops/sec | Single transformation from public ID |
Multi-Tenant Encode | ~140,500 ops/sec | Single tenant transformation |
Multi-Tenant (5 tenants) | ~27,800 ops/sec | Round-robin across tenants |
API Response (10 IDs) | ~13,700 ops/sec | Batch ID hiding simulation |
Security Validation | ~56,900 ops/sec | Key isolation verification |
- Zero allocations during transformations (reuses input buffers when possible)
- 16 bytes per UUID128 (native Buffer)
- 16 bytes per UUIDv47Key (two bigints)
- Minimal overhead - direct memory operations
- Consistent Performance: Sub-millisecond latency for single operations
- Scalable Batching: Efficient batch processing for high-throughput scenarios
- Memory Efficient: Direct buffer operations without unnecessary allocations
- CPU Optimized: BigInt arithmetic optimized for 64-bit operations
- Cache Friendly: Lookup tables and inlined operations for critical paths
- BigInt arithmetic for 64-bit operations
- Buffer reuse where possible
- Lookup tables for hex conversion
- Inlined operations for critical paths
- TypeScript optimizations compiled to efficient JavaScript
- SipHash-2-4 optimized implementation with minimal overhead
- Runtime: Bun/Node.js on modern CPU
- Iterations: 50K-100K+ per test for statistical significance
- Methodology: Vitest benchmark framework with multiple runs
- Hardware: Results may vary based on hardware configuration
β οΈ Critical: Proper key management is essential for security. Follow these guidelines to ensure safe implementation.
- ποΈ Secure Storage: Store keys in Hardware Security Modules (HSM), key vaults, or encrypted storage
- π’ Environment Isolation: Use different keys per tenant/environment to prevent cross-contamination
- π Key Rotation: Implement periodic key rotation with proper migration strategies
- π Logging Policy: Never log or transmit keys in plaintext - treat them as sensitive credentials
- π Version Validation: Always validate UUID versions before processing to prevent attacks
- 𧬠Key Derivation: Consider using key derivation functions from master secrets for scalability
- π Access Control: Implement strict access controls for key management operations
- π Audit Logging: Log all key usage and UUID transformation operations for security monitoring
- π Transport Security: Use TLS/HTTPS for all communications involving UUIDs or keys
- π§ͺ Testing: Regularly test your key rotation and recovery procedures
- SipHash-2-4: Cryptographically secure PRF resistant to timing attacks
- Key Isolation: Different keys produce completely different facades for the same UUID
- Tamper Detection: Any modification to facade results in different decoded UUID
- Forward Security: Compromising current key doesn't affect past transformations with different keys
The library includes comprehensive test coverage with >80% code coverage:
# Run all tests
bun test
# Run with coverage report
bun run test:coverage
# Watch mode for development
bun run test:watch
# UI mode with coverage
bun run test:ui
- Unit Tests: All public methods and edge cases
- Integration Tests: Real-world usage patterns
- Security Tests: Key isolation and tamper detection
- Performance Tests: Benchmark critical operations
- Compatibility Tests: Cross-platform validation
β Basic encode/decode operations (50 tests)
β Key management and validation (25 tests)
β Error handling and edge cases (30 tests)
β Security and isolation tests (20 tests)
β Performance benchmarks (10 tests)
Coverage: 85.2% (lines), 90.1% (functions), 82.7% (branches)
# Clone repository
git clone https://github.com/ali-master/uuidv47.git
cd uuidv47
# Install dependencies (Bun recommended)
bun install
# Run development build
bun run dev
# Run tests
bun run test
# Run benchmarks
bun run test:bench
# Build for production
bun run build
We welcome contributions! Please see our Contributing Guidelines for details.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Make your changes with tests
- Ensure all tests pass (
bun test
) - Commit your changes (
git commit -m 'feat: Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- SipHash Algorithm: Jean-Philippe Aumasson and Daniel J. Bernstein
- UUID Standards: RFC 4122 and RFC 9562
- TypeScript Community: For excellent tooling and ecosystem
Built with β€οΈ by Ali Torki and contributors
β Star us on GitHub β’ π¦ Follow on LinkedIn β’ π¦ View on NPM