Class: Tapyrus::SLIP39::SSS

Inherits:
Object
  • Object
show all
Extended by:
Util
Includes:
Util
Defined in:
lib/tapyrus/slip39/sss.rb

Overview

Shamir’s Secret Sharing

Constant Summary

Constants included from Util

Util::DIGEST_NAME_SHA256

Class Method Summary collapse

Methods included from Util

byte_to_bit, calc_checksum, decode_base58_address, double_sha256, encode_base58_address, hash160, hmac_sha256, pack_boolean, pack_var_int, pack_var_string, padding_zero, sha256, unpack_boolean, unpack_var_int, unpack_var_int_from_io, unpack_var_string, valid_address?

Class Method Details

.recover_secret(shares, passphrase: "") ⇒ String

recovery master secret form shares.

Usage

shares: An array of shares required for recovery. master_secret = Tapyrus::SLIP39::SSS.recover_secret(shares, passphrase: ‘xxx’)

Parameters:

  • shares (Array[Tapyrus::SLIP30::Share])

    an array of shares.

  • passphrase (String) (defaults to: "")

    the passphrase using decrypt master secret.

Returns:

  • (String)

    a master secret.

Raises:

  • (ArgumentError)


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/tapyrus/slip39/sss.rb', line 100

def self.recover_secret(shares, passphrase: "")
  raise ArgumentError, "share is empty." if shares.nil? || shares.empty?
  groups = {}
  id = shares[0].id
  exp = shares[0].iteration_exp
  group_threshold = shares.first.group_threshold
  group_count = shares.first.group_count

  shares.each do |share|
    raise ArgumentError, "Invalid set of shares. All shares must have the same id." unless id == share.id
    unless group_threshold == share.group_threshold
      raise ArgumentError, "Invalid set of shares. All shares must have the same group threshold."
    end
    unless group_count == share.group_count
      raise ArgumentError, "Invalid set of shares. All shares must have the same group count."
    end
    unless exp == share.iteration_exp
      raise ArgumentError, "Invalid set of shares. All Shares must have the same iteration exponent."
    end
    groups[share.group_index] ||= []
    groups[share.group_index] << share
  end

  group_shares = {}
  groups.each do |group_index, shares|
    member_threshold = shares.first.member_threshold
    if shares.length < member_threshold
      raise ArgumentError,
            "Wrong number of mnemonics. Threshold is #{member_threshold}, but share count is #{shares.length}"
    end
    if shares.length == 1 && member_threshold == 1
      group_shares[group_index] = shares.first.value
    else
      value_length = shares.first.value.length
      x_coordinates = []
      shares.each do |share|
        unless member_threshold == share.member_threshold
          raise ArgumentError, "Invalid set of shares. All shares in a group must have the same member threshold."
        end
        unless value_length == share.value.length
          raise ArgumentError, "Invalid set of shares. All share values must have the same length."
        end
        x_coordinates << share.member_index
      end
      x_coordinates.uniq!
      unless x_coordinates.size == shares.size
        raise ArgumentError, "Invalid set of shares. Share indices must be unique."
      end
      interpolate_shares = shares.map { |s| [s.member_index, s.value] }

      secret = interpolate(interpolate_shares, SECRET_INDEX)
      digest_value = interpolate(interpolate_shares, DIGEST_INDEX).htb
      digest, random_value = digest_value[0...DIGEST_LENGTH_BYTES].bth, digest_value[DIGEST_LENGTH_BYTES..-1].bth
      recover_digest = create_digest(secret, random_value)
      raise ArgumentError, "Invalid digest of the shared secret." unless digest == recover_digest

      group_shares[group_index] = secret
    end
  end

  return decrypt(group_shares.values.first, passphrase, exp, id) if group_threshold == 1

  if group_shares.length < group_threshold
    raise ArgumentError,
          "Wrong number of mnemonics. Group threshold is #{group_threshold}, but share count is #{group_shares.length}"
  end

  interpolate_shares = group_shares.map { |k, v| [k, v] }
  secret = interpolate(interpolate_shares, SECRET_INDEX)
  digest_value = interpolate(interpolate_shares, DIGEST_INDEX).htb
  digest, random_value = digest_value[0...DIGEST_LENGTH_BYTES].bth, digest_value[DIGEST_LENGTH_BYTES..-1].bth
  recover_digest = create_digest(secret, random_value)
  raise ArgumentError, "Invalid digest of the shared secret." unless digest == recover_digest

  decrypt(secret, passphrase, exp, id)
end

.setup_shares(groups: [], group_threshold: nil, exp: 0, secret: nil, passphrase: "") ⇒ Array[Array[Tapyrus::SLIP39::Share]]

Create SSS shares.

Usage

4 groups shares.

two for Alice

one for friends(required 3 of her 5 friends) and

one for family members(required 2 of her 6 family)

Two of these group shares are required to reconstruct the master secret. groups = [1, 1], [1, 1], [3, 5], [2, 6]

group_shares = Tapyrus::SLIP39::SSS.setup_shares(group_threshold: 2, groups: groups, secret: ‘secret with hex format’, passphrase: ‘xxx’) return 4 group array of Tapyrus::SLIP39::Share

Get each share word groups[1].to_words

> [“shadow”, “pistol”, “academic”, “always”, “adequate”, “wildlife”, “fancy”, “gross”, “oasis”, “cylinder”, “mustang”, “wrist”, “rescue”, “view”, “short”, “owner”, “flip”, “making”, “coding”, “armed”]

Parameters:

  • groups (Array[Array[Integer, Integer]]) (defaults to: [])
  • group_threshold (Integer) (defaults to: nil)

    threshold number of group shares required to reconstruct the master secret.

  • exp (Integer) (defaults to: 0)

    Iteration exponent. default is 0.

  • secret (String) (defaults to: nil)

    master secret with hex format.

  • passphrase (String) (defaults to: "")

    the passphrase used for encryption/decryption.

Returns:

Raises:

  • (ArgumentError)


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/tapyrus/slip39/sss.rb', line 34

def self.setup_shares(groups: [], group_threshold: nil, exp: 0, secret: nil, passphrase: "")
  raise ArgumentError, "Groups is empty." if groups.empty?
  raise ArgumentError, "Group threshold must be greater than 0." if group_threshold.nil? || group_threshold < 1
  raise ArgumentError, "Master secret does not specified." unless secret
  if (secret.htb.bytesize * 8) < MIN_STRENGTH_BITS
    raise ArgumentError,
          "The length of the master secret (#{secret.htb.bytesize} bytes) must be at least #{MIN_STRENGTH_BITS / 8} bytes."
  end
  unless secret.bytesize.even?
    raise ArgumentError, "The length of the master secret in bytes must be an even number."
  end
  unless passphrase.ascii_only?
    raise ArgumentError, "The passphrase must contain only printable ASCII characters (code points 32-126)."
  end
  if group_threshold > groups.length
    raise ArgumentError,
          "The requested group threshold (#{group_threshold}) must not exceed the number of groups (#{groups.length})."
  end
  groups.each do |threshold, count|
    raise ArgumentError, "Group threshold must be greater than 0." if threshold.nil? || threshold < 1
    if threshold > count
      raise ArgumentError,
            "The requested member threshold (#{threshold}) must not exceed the number of share (#{count})."
    end
    if threshold == 1 && count > 1
      raise ArgumentError,
            "Creating multiple member shares with member threshold 1 is not allowed. Use 1-of-1 member sharing instead."
    end
  end

  id = SecureRandom.random_number(32_767) # 32767 is max number for 15 bits.
  ems = encrypt(secret, passphrase, exp, id)

  group_shares = split_secret(group_threshold, groups.length, ems)

  shares =
    group_shares.map.with_index do |s, i|
      group_index, group_share = s[0], s[1]
      member_threshold, member_count = groups[i][0], groups[i][1]
      shares = split_secret(member_threshold, member_count, group_share)
      shares.map do |member_index, member_share|
        share = Tapyrus::SLIP39::Share.new
        share.id = id
        share.iteration_exp = exp
        share.group_index = group_index
        share.group_threshold = group_threshold
        share.group_count = groups.length
        share.member_index = member_index
        share.member_threshold = member_threshold
        share.value = member_share
        share.checksum = share.calculate_checksum
        share
      end
    end
  shares
end