juliolinarez

juliolinarez

ExCuid2 - Elixir implementation of CUID2 (Collision-Resistant Unique Identifiers)

ExCuid2 generates secure, collision-resistant unique identifiers designed for efficiency and horizontal scaling. They are an excellent choice for primary keys in distributed databases. Includes a type to work with Ecto

Features:

  • Collision-Resistant: Uses multiple entropy sources to minimize the probability of collisions, even in high-concurrency systems.
  • Secure: Starts with a random letter to prevent enumeration attacks and uses :crypto.strong_rand_bytes for cryptographically secure entropy.
  • Scalable: Includes a process fingerprint to ensure uniqueness across different nodes and application restarts.
  • Efficient: Implemented with a stateful Agent to manage an atomic counter quickly and safely.
  • Customizable: Allows generating IDs with a length between 24 and 32 characters.
  • Supervisable: Can be added directly to your application’s supervision tree.

Thanks

Most Liked

garrison

garrison

This is not a comment on your implementation, but I had never heard of these before and had a look at the original repo and I have to say it gives off some weird vibes. It seems like their main argument is that UUIDv4 is bad if you use a weak source of entropy (yeah, obviously) and that CUID is better because it combines “multiple sources of entropy”.

There are a lot of weird claims about security and other things. The section on k-sortability is particularly bizarre (“cloud databases” store all their data in memory so it doesn’t matter? seriously?).

Anyway, :crypto.strong_rand_bytes() should have sufficient entropy to generate ids, or at least I hope it does! Is there any other functionality that CUID2 offers over the (well-standardized) UUIDv4/7?

Of course, if someone is already using these it’s still good to have a library for them, so definitely don’t take my comments as directed at you :slight_smile:

garrison

garrison

I really don’t mean to be rude here, but I would appreciate it if you could reply in your own words rather than with an LLM.

This entire reply is AI slop. Almost every part of it is completely wrong, and there’s no way for me to know whether it’s because you’re mistaken or because GlazeGPT is hallucinating nonsense.

I had a look at your code because I was curious and it seems to me to be AI generated as well. The implementation is not good. :crypto.strong_rand_bytes/1 is cryptographically secure and provides sufficient entropy.

You do not need to start an agent with a counter (serializing every single id generation via message passing in the process and incurring massive overhead) and then sha256 that with the actual random bytes. That does not add entropy. It’s completely absurd.

You are choosing the first letter with Enum.random(). That function is not crypto-safe, all you’re doing is decreasing the entropy of your ids.

Under no circumstances should anyone ever use this ridiculous id format for a new project, but if you have a legacy project you need to support then you can just generate random bytes with :crypto.strong_rand_bytes/1 and then base36-encode them directly. You do not need to implement any of this additional “entropy” nonsense.

I recommend you update your library to do exactly that.

garrison

garrison

Instead of using the original algorithm, you can just generate bytes with :crypto.strong_rand_bytes(n) and then base36-encode them (you already have code for this I think). That would produce a valid id with sufficient entropy.

I don’t know what the purpose of the letter thing is, but I guess it’s part of the spec so fair enough. The problem is that you’re generating the first letter with a weak source (Enum.random() with the defaults), so it’s not crypto-safe like the rest of the id. Maybe this matters in practice, maybe not, but it’s not ideal. For the record the original implementation also just uses Math.random() to do this (you know, the function they themselves are claiming is unsafe), which I find quite incredible, but whatever.

I believe you can seed using :crypto.rand_seed() to make it crypto-safe, but that will of course clobber the default random state for that process:

:crypto.rand_seed()
letter = String.graphemes("abcd...") |> Enum.random()

You can also create a state with :crypto.rand_seed_s/0 and then pass it to :rand.uniform_s/2 to do the same without clobbering the default PRNG for other purposes. I’m not sure of the tradeoffs between that and using :crypto.strong_rand_bytes/1, but perhaps somebody else could comment on that.

mpraski

mpraski

Thank you for working on this library!

I’ve noticed you’re serializing access to the integer counter with an Agent - wouldn’t that become a performance issue in a high concurrency setting? Admittedly I didn’t read the CUID2 spec yet, but it doesn’t seem this ID was designed for distributed systems like the mentioned alternatives.

As for URL-friendliness I agree. I’ve come to use a “proxy” Ecto type that encodes the UUID using base62 and adds a prefix a’la Stripe identifiers. This results in a contiguous string (Dan Schultzer: Prefixed base62 UUIDv7 Object IDs with Ecto).

garrison

garrison

It’s fine to use them as DOM ids, but apparently it means you have to escape CSS selectors because they can’t start with numbers. I can see how that would be annoying, but in practice you rarely scope CSS to ids. Maybe for a weird case where you are using querySelector() with an id selector. I’ve never run into this before, personally.

It certainly would, but more importantly it’s completely pointless and, if anything, weakens the entropy of the ids. The purpose of the counter in the original implementation was to prevent sub-ms collisions from the clock source because they only had access to a millisecond timestamp in JS. This does not apply here, first of all because we have microsecond timestamps, but also because again :crypto.strong_rand_bytes() already has sufficient entropy.

Yes, it’s important to understand that the underlying format and the format you present can be entirely decoupled. A UUIDv4 is literally just 16 random bytes save for 6 bits which are set in a particular place to tell you “this is a UUIDv4”. For the other UUIDv* formats, a couple of those bits are different. You can encode the UUID however you want!

I have personally grown somewhat fond of base16 with the hyphens stripped. Base32/64 are really not meaningfully shorter from a UI perspective IMO. Subjectively, I mean.

Where Next?

Popular in Libraries Top

grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
cjen07
parameterized pipe in elixir: |n> edit: negative index in |n> and mixed usage with |> are supported example: use ParamP...
New
tfwright
After working on it for a couple of months and using it in production for most of that time, today I’ve released LiveAdmin, a LiveView ba...
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
ahamez
Hi everyone, I’ve been working on this protobuf library for 3 years. We use it in the company I work for, EasyMile, to communicate with ...
New
tmbb
I’ve decided to create this topic to discuss optimization possibilities for something like Phoenix LiveView. I’ve created this topic unde...
New
mtrudel
Bandit is an HTTP server for Plug and WebSock apps. Bandit is written entirely in Elixir and is built atop Thousand Island. It can serve...
New
gjaldon
As the title states, EctoEnum has just been updated after some time of hardly any activity in the repo. Here’s the latest release: https:...
New
anshuman23
Hello all, I have been working on my proposed project called Tensorflex as part of Google Summer of Code 2018.. Tensorflex can be used f...
New
maltoe
Hello! Came here to announce ChromicPDF, a pet project PDF generator I’ve been working on for the past few months. Why another PDF gener...
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New

Sub Categories:

We're in Beta

About us Mission Statement