Module: Codex32

Included in:
Share
Defined in:
lib/codex32.rb,
lib/codex32/share.rb,
lib/codex32/errors.rb,
lib/codex32/version.rb

Overview

Codex32 library.

Defined Under Namespace

Modules: Errors Classes: Share

Constant Summary collapse

HRP =
"ms"
SEPARATOR =
"1"
CHARSET =

stree-ignore

%w[q p z r y 9 x 8 g f 2 t v d w 0 s 3 j n 5 4 k h c e 6 m u a 7 l].freeze
BECH32_INV =

stree-ignore

[0, 1, 20, 24, 10, 8, 12, 29, 5, 11, 4, 9, 6, 28, 26, 31,
22, 18, 17, 23, 2, 25, 16, 19, 3, 21, 14, 30, 13, 7, 27, 15].freeze
MS32_CONST =
0x10ce0795c2fd1e62a
MS32_LONG_CONST =
0x43381e570bf4798ab26
SECRET_INDEX =
"s"
VERSION =
"0.1.0"

Class Method Summary collapse

Class Method Details

.array_to_bech32(data) ⇒ String

Convert array to bech32 string.

Parameters:

  • data (Array(Integer))

    An array.

Returns:

  • (String)

    bech32 string.



119
120
121
# File 'lib/codex32.rb', line 119

def array_to_bech32(data)
  data.map { |d| CHARSET[d] }.join
end

.bech32_lagrange(data, x) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
# File 'lib/codex32.rb', line 214

def bech32_lagrange(data, x)
  n = 1
  c = []
  data.each do |i|
    n = bech32_mul(n, i ^ x)
    m = 1
    data.each { |j| m = bech32_mul(m, (i == j ? x : i) ^ j) }
    c << m
  end
  c.map { |i| bech32_mul(n, BECH32_INV[i]) }
end

.bech32_mul(a, b) ⇒ Object



226
227
228
229
230
231
232
233
234
# File 'lib/codex32.rb', line 226

def bech32_mul(a, b)
  result = 0
  5.times do |i|
    result ^= ((b >> i) & 1).zero? ? 0 : a
    a *= 2
    a ^= a >= 32 ? 41 : 0
  end
  result
end

.bech32_to_array(bech32_str) ⇒ Array(Integer)

Convert bech32 string to array.

Parameters:

  • bech32_str (String)

    bech32 string.

Returns:

  • (Array(Integer))

    array of bech32 data.



74
75
76
77
78
79
80
# File 'lib/codex32.rb', line 74

def bech32_to_array(bech32_str)
  bech32_str.downcase.each_char.map do |c|
    i = CHARSET.index(c)
    raise Errors::InvalidBech32Character if i.nil?
    i
  end
end

.convert_bits(data, from, to, padding: true) ⇒ Array

Convert a data where each byte is encoding from bits to a byte slice where each byte is encoding to bits.

Parameters:

  • data (Array)
  • from (Integer)
  • to (Integer)
  • padding (Boolean) (defaults to: true)

Returns:

  • (Array)


167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/codex32.rb', line 167

def convert_bits(data, from, to, padding: true)
  acc = 0
  bits = 0
  ret = []
  maxv = (1 << to) - 1
  max_acc = (1 << (from + to - 1)) - 1
  data.each do |v|
    return nil if v.negative? || (v >> from) != 0
    acc = ((acc << from) | v) & max_acc
    bits += from
    while bits >= to
      bits -= to
      ret << ((acc >> bits) & maxv)
    end
  end
  ret << ((acc << (to - bits)) & maxv) if padding && bits != 0
  ret
end

.from(seed:, id:, share_index:, threshold: 0) ⇒ Codex32::Share

Create codex32 string.

Parameters:

  • seed (String)

    Secret with hex format.

  • threshold (Integer) (defaults to: 0)

    Threshold value.

  • id (String)

    Identifier.

  • share_index (String)

    Index of share.

Returns:

Raises:



60
61
62
63
64
65
66
67
68
69
# File 'lib/codex32.rb', line 60

def from(seed:, id:, share_index:, threshold: 0)
  raise Errors::InvalidThreshold unless threshold.is_a?(Integer)
  raise Errors::InvalidIdentifier unless id.length == 4
  raise Errors::InvalidBech32Character if CHARSET.index(share_index).nil?
  payload =
    array_to_bech32(
      convert_bits([seed].pack("H*").unpack("C*"), 8, 5, padding: true)
    )
  Share.new(id, threshold, share_index, payload)
end

.generate_share(shares, share_index) ⇒ Codex32::Share

Recover secret using shares.

Parameters:

  • shares (Array(Codex32::Share))

    Array of share.

  • share_index (String)

    A share index.

Returns:

Raises:

  • (ArgumentError)


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/codex32.rb', line 86

def generate_share(shares, share_index)
  raise ArgumentError, "shares must be array." unless shares.is_a?(Array)
  raise IdentifierMismatch unless shares.map(&:id).uniq.length == 1
  threshold = shares.map(&:threshold).uniq
  threshold.delete(0)
  raise Errors::ThresholdMismatch unless threshold.length == 1
  index = CHARSET.index(share_index.downcase)
  raise Errors::InvalidBech32Character if index.nil?
  indices = shares.map(&:index).uniq
  unless indices.length == shares.length
    raise ArgumentError, "Share index duplicate."
  end
  raise Errors::DuplicateShareIndex if indices.first == index
  raise Errors::InsufficientShares if shares.length < shares[0].threshold

  data =
    shares.map do |share|
      bech32_to_array(
        share.threshold.to_s + share.id + share.index + share.payload
      )
    end
  result = interpolate_at(data, index)
  Share.new(
    shares.first.id,
    threshold.first,
    CHARSET[result[5]],
    array_to_bech32(result[6..])
  )
end

.interpolate_at(data, x) ⇒ Object

Interpolate a set of shares to derive a share at a specific index. Each share is an array of the following data transformed in a bech32 table: threshold + id + index + payload.

Parameters:

  • data (Array(Integer))

    A set of shares.

  • x (Integer)

    index value.



191
192
193
194
195
196
197
198
199
# File 'lib/codex32.rb', line 191

def interpolate_at(data, x)
  indices = data.map { |d| d[5] }
  w = bech32_lagrange(indices, x)
  data.first.length.times.map do |i|
    n = 0
    data.length.times { |j| n ^= bech32_mul(w[j], data[j][i]) }
    n
  end
end

.long_polymod(data) ⇒ Array(Integer)

Parameters:

  • data (Array(Integer))

Returns:

  • (Array(Integer))


144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/codex32.rb', line 144

def long_polymod(data)
  generators = [
    0x3d59d273535ea62d897,
    0x7a9becb6361c6c51507,
    0x543f9b7e6c38d8a2a0e,
    0x0c577eaeccf1990d13c,
    0x1887f74f8dc71b10651
  ]
  residue = 0x23181b3
  data.each do |d|
    b = residue >> 70
    residue = (residue & 0x3fffffffffffffffff) << 5 ^ d
    5.times { |i| residue ^= ((b >> i) & 1).zero? ? 0 : generators[i] }
  end
  residue
end

.parse(codex32) ⇒ Codex32::Share

Parse codex32 string.

Parameters:

  • codex32 (String)

    Codex32 string

Returns:

Raises:



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/codex32.rb', line 30

def parse(codex32)
  if codex32.downcase != codex32 && codex32.upcase != codex32
    raise Errors::InvalidCase
  end
  hrp, remain = codex32.downcase.split(SEPARATOR)
  raise Errors::InvalidHRP unless hrp.downcase == HRP
  raise Errors::InvalidLength if codex32.length < 48 || codex32.length > 127
  raise Errors::SeparatorNotFound if remain.nil?
  unless valid_checksum?(bech32_to_array(remain))
    raise Errors::InvalidChecksum
  end

  checksum_len = remain.chars.length <= 93 ? 13 : 15

  remain = remain.chars
  threshold = remain[0].to_i
  raise Errors::InvalidThreshold unless threshold.to_s == remain[0]
  id = remain[1..4].join
  share_index = remain[5]
  payload_end = remain.length - checksum_len
  payload = remain[6...payload_end].join
  Share.new(id, threshold, share_index, payload)
end

.polymod(data) ⇒ Array(Integer)

Parameters:

  • data (Array(Integer))

Returns:

  • (Array(Integer))


125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/codex32.rb', line 125

def polymod(data)
  generators = [
    0x19dc500ce73fde210,
    0x1bfae00def77fe529,
    0x1fbd920fffe7bee52,
    0x1739640bdeee3fdad,
    0x07729a039cfc75f5a
  ]
  residue = 0x23181b3
  data.each do |d|
    b = residue >> 60
    residue = (residue & 0x0fffffffffffffff) << 5 ^ d
    5.times { |i| residue ^= ((b >> i) & 1).zero? ? 0 : generators[i] }
  end
  residue
end

.valid_checksum?(data) ⇒ Boolean

Check whether checksum is valid or not.

Parameters:

  • data (Array(Integer))

    A part as a list of integers representing the characters converted.

Returns:

  • (Boolean)


204
205
206
207
208
209
210
211
212
# File 'lib/codex32.rb', line 204

def valid_checksum?(data)
  if data.length <= 93
    polymod(data) == MS32_CONST
  elsif data.length >= 96
    long_polymod(data) == MS32_LONG_CONST
  else
    false
  end
end