Module: Philiprehberger::BaseConvert

Defined in:
lib/philiprehberger/base_convert.rb,
lib/philiprehberger/base_convert/base32.rb,
lib/philiprehberger/base_convert/base58.rb,
lib/philiprehberger/base_convert/base62.rb,
lib/philiprehberger/base_convert/base85.rb,
lib/philiprehberger/base_convert/version.rb

Defined Under Namespace

Modules: Base32, Base58, Base62, Base85 Classes: Error

Constant Summary collapse

GENERIC_ALPHABET =
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
HEX_ALPHABET =
'0123456789abcdefABCDEF'
BASE64_ALPHABET =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=-_'
BASE85_ALPHABET =
"#{(33..117).map(&:chr).join}z".freeze
DETECTION_ORDER =
[
  [:hex,    HEX_ALPHABET],
  [:base32, Base32::DECODE_MAP.keys.join],
  [:base58, Base58::ALPHABET],
  [:base62, Base62::ALPHABET],
  [:base64, BASE64_ALPHABET],
  [:base85, BASE85_ALPHABET]
].map { |name, chars| [name, chars.each_char.to_a.to_set] }.freeze
VERSION =
'0.6.0'

Class Method Summary collapse

Class Method Details

.base32_decode(string) ⇒ String

Decode a Crockford Base32 string

Parameters:

  • string (String)

    the Base32-encoded string

Returns:

  • (String)

    the decoded string

Raises:

  • (Error)

    if the string contains invalid characters



130
131
132
# File 'lib/philiprehberger/base_convert.rb', line 130

def self.base32_decode(string)
  Base32.decode(string)
end

.base32_encode(string) ⇒ String

Encode a string to Crockford Base32

Parameters:

  • string (String)

    the input string

Returns:

  • (String)

    the Base32-encoded string



121
122
123
# File 'lib/philiprehberger/base_convert.rb', line 121

def self.base32_encode(string)
  Base32.encode(string)
end

.base36_decode(string) ⇒ Integer

Decode a Base36 string to an integer

Parameters:

  • string (String)

    the Base36-encoded string

Returns:

  • (Integer)

    the decoded integer

Raises:

  • (Error)

    if the string contains invalid characters



165
166
167
# File 'lib/philiprehberger/base_convert.rb', line 165

def self.base36_decode(string)
  decode(string, base: 36)
end

.base36_encode(integer) ⇒ String

Encode an integer to Base36

Parameters:

  • integer (Integer)

    the input integer (must be >= 0)

Returns:

  • (String)

    the Base36-encoded string

Raises:

  • (Error)

    if the input is negative



156
157
158
# File 'lib/philiprehberger/base_convert.rb', line 156

def self.base36_encode(integer)
  encode(integer, base: 36)
end

.base58_decode(string) ⇒ String

Decode a Base58 string

Parameters:

  • string (String)

    the Base58-encoded string

Returns:

  • (String)

    the decoded string

Raises:

  • (Error)

    if the string contains invalid characters



77
78
79
# File 'lib/philiprehberger/base_convert.rb', line 77

def self.base58_decode(string)
  Base58.decode(string)
end

.base58_decode_int(string) ⇒ Integer

Decode a Base58 string to an integer

Parameters:

  • string (String)

    the Base58-encoded string

Returns:

  • (Integer)

    the decoded integer

Raises:

  • (ArgumentError)

    if the string is empty or contains invalid characters



95
96
97
# File 'lib/philiprehberger/base_convert.rb', line 95

def self.base58_decode_int(string)
  Base58.decode_int(string)
end

.base58_encode(string) ⇒ String

Encode a string to Base58 (Bitcoin alphabet)

Parameters:

  • string (String)

    the input string

Returns:

  • (String)

    the Base58-encoded string



68
69
70
# File 'lib/philiprehberger/base_convert.rb', line 68

def self.base58_encode(string)
  Base58.encode(string)
end

.base58_encode_int(integer) ⇒ String

Encode a non-negative integer to Base58 (Bitcoin alphabet)

Parameters:

  • integer (Integer)

    the input integer (must be >= 0)

Returns:

  • (String)

    the Base58-encoded string

Raises:

  • (ArgumentError)

    if the input is negative or not an integer



86
87
88
# File 'lib/philiprehberger/base_convert.rb', line 86

def self.base58_encode_int(integer)
  Base58.encode_int(integer)
end

.base62_decode(string) ⇒ Integer

Decode a Base62 string to an integer

Parameters:

  • string (String)

    the Base62-encoded string

Returns:

  • (Integer)

    the decoded integer

Raises:

  • (Error)

    if the string contains invalid characters



113
114
115
# File 'lib/philiprehberger/base_convert.rb', line 113

def self.base62_decode(string)
  Base62.decode(string)
end

.base62_encode(integer) ⇒ String

Encode an integer to Base62

Parameters:

  • integer (Integer)

    the input integer (must be >= 0)

Returns:

  • (String)

    the Base62-encoded string

Raises:

  • (Error)

    if the input is negative



104
105
106
# File 'lib/philiprehberger/base_convert.rb', line 104

def self.base62_encode(integer)
  Base62.encode(integer)
end

.base85_decode(string) ⇒ String

Decode an ASCII85 string

Parameters:

  • string (String)

    the ASCII85-encoded string

Returns:

  • (String)

    the decoded string

Raises:

  • (Error)

    if the string contains invalid characters



147
148
149
# File 'lib/philiprehberger/base_convert.rb', line 147

def self.base85_decode(string)
  Base85.decode(string)
end

.base85_encode(string) ⇒ String

Encode a string to ASCII85

Parameters:

  • string (String)

    the input string

Returns:

  • (String)

    the ASCII85-encoded string



138
139
140
# File 'lib/philiprehberger/base_convert.rb', line 138

def self.base85_encode(string)
  Base85.encode(string)
end

.decode(string, base:) ⇒ Integer

Decode a string from an arbitrary base (2-62) to an integer

Parameters:

  • string (String)

    the encoded string

  • base (Integer)

    the source base (2-62)

Returns:

  • (Integer)

    the decoded integer

Raises:

  • (Error)

    if the base is out of range or string contains invalid characters



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/philiprehberger/base_convert.rb', line 224

def self.decode(string, base:)
  raise Error, 'base must be between 2 and 62' unless base.between?(2, 62)
  raise Error, 'input must be a non-empty string' if string.nil? || string.empty?

  alphabet = GENERIC_ALPHABET[0, base]
  num = 0

  string.each_char do |char|
    value = alphabet.index(char)
    raise Error, "invalid character for base #{base}: #{char}" if value.nil?

    num = (num * base) + value
  end

  num
end

.detect(string) ⇒ Symbol?

Detect the encoding of a string based on its character set

Returns the narrowest base whose alphabet fully covers the input. Empty strings and strings that match no known base return nil.

Parameters:

  • string (String)

    the input string

Returns:

  • (Symbol, nil)

    one of :hex, :base32, :base58, :base62, :base64, :base85, or nil



37
38
39
40
41
42
43
# File 'lib/philiprehberger/base_convert.rb', line 37

def self.detect(string)
  return nil if string.nil? || string.empty?

  unique_chars = string.each_char.to_a.to_set
  match = DETECTION_ORDER.find { |(_name, alphabet)| unique_chars.subset?(alphabet) }
  match&.first
end

.encode(integer, base:) ⇒ String

Encode an integer in an arbitrary base (2-62)

Parameters:

  • integer (Integer)

    the input integer (must be >= 0)

  • base (Integer)

    the target base (2-62)

Returns:

  • (String)

    the encoded string

Raises:

  • (Error)

    if the base is out of range or input is negative



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/philiprehberger/base_convert.rb', line 200

def self.encode(integer, base:)
  raise Error, 'base must be between 2 and 62' unless base.between?(2, 62)
  raise Error, 'input must be a non-negative integer' unless integer.is_a?(Integer) && integer >= 0

  return GENERIC_ALPHABET[0] if integer.zero?

  alphabet = GENERIC_ALPHABET[0, base]
  result = []
  num = integer

  while num.positive?
    num, remainder = num.divmod(base)
    result << alphabet[remainder]
  end

  result.reverse.join
end

.hex_decode(string) ⇒ String

Decode a hexadecimal string

Parameters:

  • string (String)

    the hex-encoded string

Returns:

  • (String)

    the decoded string

Raises:

  • (Error)

    if the input is nil, empty, odd-length, or contains invalid characters



185
186
187
188
189
190
191
192
# File 'lib/philiprehberger/base_convert.rb', line 185

def self.hex_decode(string)
  raise Error, 'input cannot be nil' if string.nil?
  raise Error, 'input cannot be empty' if string.empty?
  raise Error, 'invalid hex string: odd length' if string.length.odd?
  raise Error, "invalid hex character in: #{string}" unless string.match?(/\A[0-9a-fA-F]+\z/)

  [string].pack('H*')
end

.hex_encode(string) ⇒ String

Encode a string to hexadecimal

Parameters:

  • string (String)

    the input string

Returns:

  • (String)

    the hex-encoded string

Raises:

  • (Error)

    if the input is nil



174
175
176
177
178
# File 'lib/philiprehberger/base_convert.rb', line 174

def self.hex_encode(string)
  raise Error, 'input cannot be nil' if string.nil?

  string.unpack1('H*')
end

.valid?(string, base) ⇒ Boolean

Check whether a string is syntactically valid for the given base

Returns true when every character in the string is part of the given base’s alphabet. Empty/nil input and unknown bases return false. Does not perform a decode — purely a character-set check.

Parameters:

  • string (String)

    the input string

  • base (Symbol)

    one of :hex, :base32, :base58, :base62, :base64, :base85

Returns:

  • (Boolean)

    true when valid



54
55
56
57
58
59
60
61
62
# File 'lib/philiprehberger/base_convert.rb', line 54

def self.valid?(string, base)
  return false if string.nil? || !string.is_a?(String) || string.empty?

  entry = DETECTION_ORDER.find { |(name, _)| name == base }
  return false unless entry

  alphabet = entry.last
  string.each_char.to_a.to_set.subset?(alphabet)
end