Skip to content

Instantly share code, notes, and snippets.

@inopinatus
Last active April 18, 2025 06:25
Show Gist options
  • Save inopinatus/78fd4e4327a826ef3005e431c3fe389c to your computer and use it in GitHub Desktop.
Save inopinatus/78fd4e4327a826ef3005e431c3fe389c to your computer and use it in GitHub Desktop.
Convert UUIDs to/from base58
module Uu58
ALPHABET58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".chars.freeze
ALPHAMAP58 = ALPHABET58.each_with_index.to_h.freeze
# Convert a string UUID to a base58 string representation.
#
# Output will be padded up to 22 digits if necessary.
#
# Behaviour is undefined if passing something other than a 128-bit
# hex string in network order with optional interstitial hyphens.
def self.uuid_to_base58(uuid, alphabet = ALPHABET58)
ary = uuid.delete("-").hex.digits(58).reverse
ary.unshift(0) while ary.length < 22
alphabet.values_at(*ary).join
end
# Convert a base58 number to a UUID.
#
# Input should be a big-endian base58 string representation of
# a whole number less than 2^128.
def self.base58_to_uuid(base58_val, alphamap = ALPHAMAP58)
num = 0
base58_val.each_char do |ch|
val = alphamap[ch] or raise ArgumentError, 'invalid base58 string'
num = num * 58 + val
end
raise RangeError, 'base58 value too large for 128‑bit UUID' unless num >> 128 == 0
"%08x-%04x-%04x-%04x-%04x%08x" % [
(num >> 96) & 0xFFFFFFFF,
(num >> 80) & 0xFFFF,
(num >> 64) & 0xFFFF,
(num >> 48) & 0xFFFF,
(num >> 32) & 0xFFFF,
num & 0xFFFFFFFF
]
end
end
require 'minitest/autorun'
require 'securerandom'
require_relative 'uu58'
class TestUu58 < Minitest::Test
def test_zero_uuid
uuid = '00000000-0000-0000-0000-000000000000'
bs = '1111111111111111111111'
assert_equal bs, Uu58.uuid_to_base58(uuid)
assert_equal uuid, Uu58.base58_to_uuid(bs)
end
def test_max_uuid
uuid = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
bs = 'YcVfxkQb6JRzqk5kF2tNLv'
assert_equal bs, Uu58.uuid_to_base58(uuid)
assert_equal uuid, Uu58.base58_to_uuid(bs)
end
def test_known_uuid_1
uuid = '00112233-4455-6677-8899-aabbccddeeff'
bs = '11UoWww8DGaVGLtea7zU7p'
assert_equal bs, Uu58.uuid_to_base58(uuid)
assert_equal uuid, Uu58.base58_to_uuid(bs)
end
def test_known_uuid_2
uuid = '01234567-89ab-cdef-0123-456789abcdef'
bs = '199dn6s7bZoVpjzYciVNgN'
assert_equal bs, Uu58.uuid_to_base58(uuid)
assert_equal uuid, Uu58.base58_to_uuid(bs)
end
def test_round_trip_random
5.times do
uuid = SecureRandom.uuid
bs = Uu58.uuid_to_base58(uuid)
assert_equal uuid.downcase, Uu58.base58_to_uuid(bs)
end
end
def test_overflow
bs = 'YcVfxkQb6JRzqk5kF2tNLw'
assert_raises(RangeError) { Uu58.base58_to_uuid(bs) }
end
def test_invalid_character
assert_raises(ArgumentError) { Uu58.base58_to_uuid('!@@@not58!!!') }
end
end
@inopinatus
Copy link
Author

Revised today (25-04-18)

  • faster conversion to uuid with current Ruby 3.4 (up to 3x faster!)
  • adding an overflow guard in conversion to uuid
  • removed implied dependency on Active Support
  • added test values

note that the optional second parameter of Uu58.base58_to_uuid is no longer an alphabet array but an inverse hash of character to digit value

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment