Skip to content

Instantly share code, notes, and snippets.

@narze
Last active February 27, 2024 21:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save narze/985712e46d33be3613e395388b549c2d to your computer and use it in GitHub Desktop.
Save narze/985712e46d33be3613e395388b549c2d to your computer and use it in GitHub Desktop.
Cuid2 implementation in Ruby, converted from cuid2.js via ChatGPT 4 with modified fingerprint creation
require "digest"
# Cuid2 in Ruby, converted from cuid2.js via ChatGPT 4
# https://github.com/paralleldrive/cuid2/blob/main/src/index.js
# Example usage
# cuid_instance = Cuid2.new
# puts cuid_instance.generate
# cuid_singleton = Cuid2.generate
# puts cuid_singleton
class Cuid2
DEFAULT_LENGTH = 24
BIG_LENGTH = 32
def initialize(length = DEFAULT_LENGTH, fingerprint_string = "")
@counter = self.class.create_counter
@fingerprint = self.class.create_fingerprint(fingerprint_string)
@length = length
end
def generate
first_letter = SecureRandom.rand(97..122).chr
time = (Time.now.to_f * 1000).to_i.to_s(36)
count = @counter.call.to_s(36)
salt = self.class.create_entropy(@length)
hash_input = "#{time}#{salt}#{count}#{@fingerprint}"
"#{first_letter}#{self.class.hash(hash_input)[1, @length - 1]}"
end
alias_method :call, :generate
# Singleton generate method
def self.generate
new.generate
end
# Create entropy
def self.create_entropy(length = 4)
entropy = ""
while entropy.length < length
entropy += SecureRandom.rand(36).to_s(36)
end
entropy
end
# Hash function using Digest::SHA512
def self.hash(input = "")
Digest::SHA512.hexdigest(input).to_i(16).to_s(36)[1..]
end
# Create fingerprint of the host environment
def self.create_fingerprint(fingerprint_string = "")
constants_string = Object.constants.map(&:to_s).join(",")
hostname = defined?(Socket) ? Socket.gethostname : ""
pid = defined?(Process) ? Process.pid.to_s : ""
source_string = "#{fingerprint_string}#{hostname}#{pid}#{constants_string}#{create_entropy(BIG_LENGTH)}"
hash(source_string)[1..BIG_LENGTH]
end
# Counter function
def self.create_counter
count = SecureRandom.rand(476782367)
proc { count += 1 }
end
# Check if a string is a valid CUID
def self.is_cuid?(id, min_length: 2, max_length: BIG_LENGTH)
regex = /^[a-z][0-9a-z]+$/
id.is_a?(String) && id.length.between?(min_length, max_length) && regex.match?(id)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment