Class: Tapyrus::SLIP39::SSS
- Inherits:
-
Object
- Object
- Tapyrus::SLIP39::SSS
- Extended by:
- Util
- Includes:
- Util
- Defined in:
- lib/tapyrus/slip39/sss.rb
Overview
Shamir’s Secret Sharing
Constant Summary
Constants included from Util
Class Method Summary collapse
-
.recover_secret(shares, passphrase: "") ⇒ String
recovery master secret form shares.
-
.setup_shares(groups: [], group_threshold: nil, exp: 0, secret: nil, passphrase: "") ⇒ Array[Array[Tapyrus::SLIP39::Share]]
Create SSS shares.
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’)
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”]
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 |