Module: AdequateCryptoAddress::Utils::MoneroBase58

Defined in:
lib/adequate_crypto_address/utils/monero_base58.rb

Overview

Monero's Base58 encodes fixed 8-byte blocks into 11 characters (shorter trailing blocks use fewer characters). Reference: https://github.com/monero-project/monero/blob/master/src/common/base58.cpp

Constant Summary collapse

ALPHABET =
'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
FULL_BLOCK_SIZE =
8
FULL_ENCODED_BLOCK_SIZE =
11
ENCODED_BLOCK_SIZES =

Decoded byte count produced by an encoded block of each valid length.

[0, 2, 3, 5, 6, 7, 9, 10, 11].freeze

Class Method Summary collapse

Class Method Details

.decode(string) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/adequate_crypto_address/utils/monero_base58.rb', line 17

def decode(string)
  return nil if string.nil? || string.empty?

  full_blocks = string.length / FULL_ENCODED_BLOCK_SIZE
  last_size = string.length % FULL_ENCODED_BLOCK_SIZE
  return nil unless ENCODED_BLOCK_SIZES.include?(last_size)

  out = []
  full_blocks.times do |i|
    block = decode_block(string[i * FULL_ENCODED_BLOCK_SIZE, FULL_ENCODED_BLOCK_SIZE], FULL_BLOCK_SIZE)
    return nil unless block

    out.concat(block)
  end
  decode_tail(string, full_blocks, last_size, out)
end

.decode_block(chars, size) ⇒ Object

Decodes one Base58 block into exactly size big-endian bytes, rejecting unknown characters and values that overflow the target byte width.



46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/adequate_crypto_address/utils/monero_base58.rb', line 46

def decode_block(chars, size)
  num = 0
  chars.each_char do |char|
    index = ALPHABET.index(char)
    return nil unless index

    num = (num * 58) + index
  end
  return nil if num >= (1 << (size * 8))

  Array.new(size) { |i| (num >> (8 * (size - 1 - i))) & 0xff }
end

.decode_tail(string, full_blocks, last_size, out) ⇒ Object



34
35
36
37
38
39
40
41
42
# File 'lib/adequate_crypto_address/utils/monero_base58.rb', line 34

def decode_tail(string, full_blocks, last_size, out)
  return out if last_size.zero?

  tail = string[full_blocks * FULL_ENCODED_BLOCK_SIZE, last_size]
  block = decode_block(tail, ENCODED_BLOCK_SIZES.index(last_size))
  return nil unless block

  out.concat(block)
end