Skip to content

Latest commit

 

History

384 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cuid.net

GitHub Workflow Status Sonar Quality Gate Sonar Coverage

Nuget Downloads Static Badge

cuid.net is a .NET library. It generates collision-resistant unique identifiers (CUIDs). Use CUIDs in distributed systems. CUIDs are an alternative to GUIDs. CUIDs are more readable than GUIDs. Some CUIDs are sortable. CUIDs have better security characteristics than GUIDs.

For more information about CUIDs, go to the official projects: CUID and CUID2.

A command-line tool, cuidgen, is also available. Use cuidgen to generate CUIDs in scripts.

Table of Contents

Features

  • Two implementations: CUIDv1 (deprecated) and CUIDv2 (recommended).
  • Collision resistance: The library generates cryptographically strong identifiers. Collisions almost never happen.
  • Horizontal scalability: Generate identifiers on many machines at the same time. The machines do not need to coordinate.
  • URL-safe format: The library uses base-36 encoding (0-9, a-z). The identifiers are clean and readable.
  • Configurable length: CUIDv2 supports lengths from 4 to 32 characters. The default length is 24 characters.
  • Type safety: The identifiers are immutable structures. They have full type safety and equality support.
  • Framework support: The library targets .NET Standard 2.0, .NET Standard 2.1, .NET 8.0, and .NET 10.0.
  • Serialization: The library has built-in JSON and XML serialization support for CUIDv1.
  • Trimming support: The library supports trimming on .NET 8 and later.
  • Compiler warnings: Use of CUIDv1 emits diagnostic VISLIB0001. This warning tells you to migrate to CUIDv2.

Installation

Install cuid.net with the NuGet Package Manager:

dotnet add package cuid.net

Or install cuid.net with the Package Manager Console:

Install-Package cuid.net

Requirements

Supported platforms:

  • .NET 8.0 and later
  • .NET Core 2.0 and later
  • .NET Framework 4.6.1 and later
  • Mono 5.4 and later
  • Xamarin.iOS 10.14 and later
  • Xamarin.Mac 3.8 and later
  • Xamarin.Android 8.0 and later
  • Universal Windows Platform 10.0.16299 and later

Dependencies:

NuGet installs the following runtime dependencies with the library.

All platforms:

  • BouncyCastle.Cryptography — provides the SHA-3 fallback hashing for CUIDv2. The library uses this package only when the runtime has no native SHA-3 512-bit implementation.
  • CommunityToolkit.Diagnostics — provides guard clauses and validation.

.NET Standard 2.0/2.1 only:

  • Microsoft.Bcl.HashCode — provides HashCode support for older frameworks.
  • PolySharp — provides compile-time language polyfills for older frameworks.
  • System.Text.Json — provides JSON serialization support for CUIDv1.

Quick Start

using Visus.Cuid;

// CUIDv2 (Recommended)
Cuid2 id = new Cuid2();
Console.WriteLine(id); // o2tm13zgjtaur83duiakvgiq

// CUIDv2 with custom length
Cuid2 shortId = new Cuid2(10);
Console.WriteLine(shortId); // rolaz6ek3u

// CUIDv1 (Deprecated - emits compiler warning VISLIB0001)
Cuid legacyId = Cuid.NewCuid();
Console.WriteLine(legacyId); // cmjj07yka00016337xrs9mj24

CUIDv2 (Recommended)

Note

Use Cuid2 for all new projects. Cuid2 generates cryptographically strong identifiers. Use these identifiers in security-sensitive contexts.

Cuid2 is an immutable structure. It generates collision-resistant identifiers with SHA-3 hashing. Cuid2 puts security first. CUIDv1 does not. Cuid2 does not reveal the generation time or location of an identifier.

CUIDv2 Features

  • Cryptographically strong: Cuid2 uses SHA-3 512-bit hashing. On .NET 8.0 and later, the library uses the runtime's native SHA-3 implementation when the OS and hardware support it. Otherwise, the library falls back to BouncyCastle.
  • No information disclosure: You cannot derive when or where the library created the identifier.
  • Variable length: Cuid2 supports identifiers from 4 to 32 characters. The default length is 24 characters.
  • Not sortable: Cuid2 does not implement IComparable. This design improves security.
  • Equality support: Cuid2 implements IEquatable<Cuid2> for comparisons.
  • No built-in serialization: Use .ToString() to get the string representation.

CUIDv2 Structure

A CUIDv2 value has a variable-length structure. The structure has no fixed pattern. The library generates a CUIDv2 value with this process:

  1. Input components:

    • Prefix: One random character (a-z).
    • Timestamp: The Unix timestamp, in ticks.
    • Counter: A session counter. The library seeds the counter from a cryptographic RNG. The library increments the counter for every identifier.
    • Fingerprint: Host-specific data. This data includes the hostname, the process ID, and environment variables.
    • Random data: Cryptographically strong random bytes. The length matches the requested identifier length.
  2. Hash computation: The library hashes all components except the prefix with SHA-3 512-bit. It uses a native implementation when the runtime and OS support one. Otherwise, it uses BouncyCastle.

  3. Encoding: The library encodes the hash in base-36. It truncates the result to the requested length minus 1. It prepends the random prefix to the result.

Example:

o2tm13zgjtaur83duiakvgiq

CUIDv2 Usage

Basic Generation

using Visus.Cuid;

// Default length (24 characters)
Cuid2 id = new Cuid2();
Console.WriteLine(id); // o2tm13zgjtaur83duiakvgiq

// Custom length (4-32 characters)
Cuid2 shortId = new Cuid2(10);
Console.WriteLine(shortId); // v1888wvo9i

Cuid2 longId = new Cuid2(32);
Console.WriteLine(longId); // zkx5dng1v8r0dg36id29uoqt1dsndmvb

String Conversion

Cuid2 defines no conversion operator. Call ToString() to get the string value.

using Visus.Cuid;

Cuid2 id = new Cuid2();

string idString = id.ToString();

// String interpolation also calls ToString()
string message = $"Generated id: {id}";

Equality Comparison

using Visus.Cuid;

Cuid2 id1 = new Cuid2();
Cuid2 id2 = new Cuid2();
Cuid2 id3 = id1;

// Equality operators
bool areEqual = id1 == id3;     // true
bool notEqual = id1 != id2;     // true

// Equals method
bool equals = id1.Equals(id3);  // true

// GetHashCode support for collections
HashSet<Cuid2> uniqueIds = new HashSet<Cuid2> { id1, id2, id3 };
Console.WriteLine(uniqueIds.Count); // 2

Default Values

Cuid2 has no Empty constant. new Cuid2(0) throws ArgumentOutOfRangeException, because the minimum length is 4.

default(Cuid2) is the only zero-value instance. Its ToString() method does not return an empty string. It returns a string of 24 zero characters, the default length.

using Visus.Cuid;

Cuid2 defaultId = default;

Console.WriteLine(defaultId.ToString()); // 000000000000000000000000
Console.WriteLine(defaultId == default); // true

Important

Technical details:

  • The fingerprint size changes with the hostname length and the environment variables.
  • The random data size matches the requested identifier length.
  • The timestamp precision is in ticks (100-nanosecond intervals). It is not in milliseconds.
  • SHA-3 is the NIST-standardized algorithm (FIPS 202). It is not the original Keccak submission.

CUIDv2 Validation

Cuid2 validates the length during construction:

using Visus.Cuid;

try
{
    // Invalid: length must be between 4 and 32
    Cuid2 tooShort = new Cuid2(3);  // throws ArgumentOutOfRangeException
    Cuid2 tooLong = new Cuid2(33);  // throws ArgumentOutOfRangeException
}
catch (ArgumentOutOfRangeException ex)
{
    Console.WriteLine($"Invalid length: {ex.Message}");
}

// Valid lengths
Cuid2 valid1 = new Cuid2(4);   // Minimum
Cuid2 valid2 = new Cuid2(24);  // Default
Cuid2 valid3 = new Cuid2(32);  // Maximum

CUIDv1 (Deprecated)

Caution

CUIDv1 is deprecated for security reasons. Migrate to Cuid2 for all new projects and for security-sensitive applications.

Warning

An observer can often work out when and where the library created a CUIDv1 value. Do not use CUIDv1 in security-sensitive contexts.

Note

Use of CUIDv1 emits the compiler warning VISLIB0001. This warning tells you to migrate to CUIDv2.

Cuid is an immutable structure. Cuid provides a sortable, string-safe alternative to Guid. Use Cuid for horizontal scaling and binary search. Use Cuid when you need in-process creation order. Use Cuid only in contexts where security is not a concern.

Security Considerations

Do not use CUIDv1 in these cases:

  • Security or privacy is a concern.
  • You must hide the generation time or location.
  • The application exposes identifiers in URLs or public APIs.
  • Compliance rules require non-predictable identifiers.

You may use CUIDv1 in these cases:

  • The identifiers are internal, in a controlled environment.
  • You need compatibility with a legacy system.
  • Sortability matters more than security.

CUIDv1 Structure

A CUIDv1 value has several data points. The library base-36 encodes the value to a fixed length of 25 characters.

Example:

cmjj07yka00016337xrs9mj24
Segment Length Source
c 1 CUIDv1 identifier prefix
mjj07yka 8 Unix timestamp in milliseconds (base-36)
0001 4 Session counter (base-36)
6337 4 Client fingerprint (process ID + hostname)
xrs9mj24 8 Random data (base-36)

Total length: 25 characters.

CUIDv1 Usage

Generation

using Visus.Cuid;

// Static factory method (recommended)
Cuid id = Cuid.NewCuid();
Console.WriteLine(id); // cmjj07yka00016337xrs9mj24

// Empty/default value
Cuid empty = Cuid.Empty;

Parsing

using Visus.Cuid;

// Constructor parsing
Cuid id1 = new Cuid("cmjj07yka00016337xrs9mj24");

// Explicit parsing
Cuid id2 = Cuid.Parse("cmjj07yka00016337xrs9mj24");

// Try-parse pattern
if (Cuid.TryParse("cmjj07yka00016337xrs9mj24", out Cuid id3))
{
    Console.WriteLine($"Parsed: {id3}");
}
else
{
    Console.WriteLine("Invalid CUID format");
}

Comparison and Sorting

Cuid implements IComparable, IComparable<Cuid>, and IEquatable<Cuid>:

CompareTo does not compare CUIDv1 values by timestamp. It compares the session counter first, then the random value, then the timestamp. Within one process, the counter increases with each call to NewCuid(). So values created earlier in the same process sort before values created later, until the counter wraps.

using Visus.Cuid;

Cuid id1 = Cuid.NewCuid();
Cuid id2 = Cuid.NewCuid();

// Comparison operators
bool isLess = id1 < id2;        // true (id1 has the lower counter value)
bool isGreater = id2 > id1;     // true
bool areEqual = id1 == id1;     // true

// CompareTo method
int comparison = id1.CompareTo(id2); // -1 (id1 sorts first)

// Sorting
List<Cuid> ids = new List<Cuid> { id2, id1 };
ids.Sort(); // Creation order: [id1, id2]

// Empty comparison
bool isEmpty = id1 == Cuid.Empty; // false

Equality

using Visus.Cuid;

Cuid id1 = Cuid.Parse("cmjj07yka00016337xrs9mj24");
Cuid id2 = Cuid.Parse("cmjj07yka00016337xrs9mj24");
Cuid id3 = Cuid.NewCuid();

// Equality operators
bool equal = id1 == id2;        // true
bool notEqual = id1 != id3;     // true

// Equals method
bool equals = id1.Equals(id2);  // true

// Hash code support
Dictionary<Cuid, string> lookup = new Dictionary<Cuid, string>
{
    { id1, "First" },
    { id3, "Second" }
};

CUIDv1 Serialization

CUIDv1 has built-in serialization support for JSON and XML.

JSON Serialization

using System.Text.Json;
using Visus.Cuid;

// Serialize
Cuid id = Cuid.NewCuid();
string json = JsonSerializer.Serialize(id);
Console.WriteLine(json); // "cmjj07yka00016337xrs9mj24"

// Deserialize
Cuid deserialized = JsonSerializer.Deserialize<Cuid>("\"cmjj07yka00016337xrs9mj24\"");

// In objects
public class Document
{
    public Cuid Id { get; set; }
    public string Content { get; set; }
}

Document doc = new Document
{
    Id = Cuid.NewCuid(),
    Content = "Example"
};
string docJson = JsonSerializer.Serialize(doc);
// {"Id":"cmjj07yka00016337xrs9mj24","Content":"Example"}

XML Serialization

using System.Xml;
using System.Xml.Serialization;
using Visus.Cuid;

// Serialize
Cuid id = Cuid.NewCuid();
XmlSerializer serializer = new XmlSerializer(typeof(Cuid));
XmlWriterSettings settings = new XmlWriterSettings { Indent = false };

using (StringWriter sw = new StringWriter())
using (XmlWriter xw = XmlWriter.Create(sw, settings))
{
    serializer.Serialize(xw, id);
    Console.WriteLine(sw.ToString());
    // <?xml version="1.0" encoding="utf-16"?><cuid>cmjj07yka00016337xrs9mj24</cuid>
}

// Deserialize
string xml = "<?xml version=\"1.0\" encoding=\"utf-16\"?><cuid>cmjj07yka00016337xrs9mj24</cuid>";
using (StringReader sr = new StringReader(xml))
using (XmlReader xr = XmlReader.Create(sr))
{
    Cuid deserialized = (Cuid)serializer.Deserialize(xr);
}

Framework Support

cuid.net targets multiple frameworks for broad compatibility:

Target Framework Version
.NET Standard 2.0, 2.1
.NET 8.0, 10.0
.NET Framework 4.6.1 and later (through .NET Standard 2.0)

Note

.NET Framework 4.6.1 is the minimum supported version. For best .NET Standard 2.0 compatibility, use .NET Framework 4.7.2 or later.

The test suite targets .NET Framework 4.8, .NET 8.0, and .NET 10.0. All three targets run in CI.

Platform-Specific Features

C# language features:

  • The library uses C# 14 language features.
  • The library uses PolySharp to support these features on older frameworks.
  • The library uses conditional compilation for framework-specific APIs.

Trimming support:

  • The library sets IsTrimmable to true on the .NET 8.0 and .NET 10.0 targets.
  • Trimming reduces the deployment size of self-contained applications.

See Installation → Dependencies for the full dependency list.

Performance Considerations

The library measures Cuid2 and Cuid performance with BenchmarkDotNet. The benchmarks/cuid.net.benchmarks project holds the benchmark code. Run the benchmarks with this command:

dotnet run -c Release --project benchmarks/cuid.net.benchmarks/cuid.net.benchmarks.csproj -- --filter '*'

The tables below come from this environment: BenchmarkDotNet v0.15.8, macOS 27.0 (26A428), Apple M2 Pro, .NET SDK 10.0.401, .NET 10.0.12 (Arm64 RyuJIT). Each Guid row is the baseline for its Ratio column. Your numbers will vary by platform and .NET version.

CUIDv2 Performance

Method Mean Ratio Allocated
new Cuid2() (default length) 20.96 μs 87.19 416 B
new Cuid2(32) (max length) 20.99 μs 87.32 456 B
Guid.NewGuid() (baseline) 240.38 ns 1.00 —
Guid.ToString() (baseline) 5.88 ns 1.00 96 B
Cuid2.ToString() 0.00 ns 0.00 —

Cuid2 construction costs more than Guid.NewGuid(). The SHA-3 512-bit hash causes most of this cost. Cuid2.ToString() returns a cached string. It costs close to nothing.

These numbers reflect whichever SHA-3 path this environment's OS and hardware select at run time — native or the BouncyCastle fallback. See CUIDv2 Features for the selection rule.

Optimization tips:

  • Cache an identifier value instead of creating a new one for the same entity.
  • Use a shorter length (4 to 10 characters) outside security-sensitive contexts.

CUIDv1 Performance

Method Mean Ratio Allocated
Cuid.NewCuid() 181.62 ns 0.75 250 B
Guid.NewGuid() (baseline) 240.77 ns 1.00 —
Guid.ToString() (baseline) 5.98 ns 1.00 96 B
Cuid.ToString() 0.00 ns 0.00 —

CUIDv1 construction costs less than Guid.NewGuid(). CUIDv1 skips the SHA-3 hash that CUIDv2 uses. Use Cuid2 in new code. Use Cuid only when you have a specific reason to.

About

.NET implementation of collision-resistant ids

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

53 stars

Watchers

2 watching

Forks

Releases

Used by

Contributors

Languages