Class: Bitcoin::SLIP39::SSS
- Inherits:
-
Object
- Object
- Bitcoin::SLIP39::SSS
- Extended by:
- Util
- Includes:
- Util
- Defined in:
- lib/bitcoin/slip39/sss.rb
Overview
Shamir’s Secret Sharing
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[Bitcoin::SLIP39::Share]]
Create SSS shares.
Methods included from Util
byte_to_bit, calc_checksum, decode_base58_address, double_sha256, encode_base58_address, hash160, hkdf_sha256, hmac_sha256, pack_boolean, pack_var_int, pack_var_string, padding_zero, sha256, tagged_hash, 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 = Bitcoin::SLIP39::SSS.recover_secret(shares, passphrase: ‘xxx’)
85 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 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 |
# File 'lib/bitcoin/slip39/sss.rb', line 85 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 raise ArgumentError, 'Invalid set of shares. All shares must have the same group threshold.' unless group_threshold == share.group_threshold raise ArgumentError, 'Invalid set of shares. All shares must have the same group count.' unless group_count == share.group_count raise ArgumentError, 'Invalid set of shares. All Shares must have the same iteration exponent.' unless exp == share.iteration_exp groups[share.group_index] ||= [] groups[share.group_index] << share end group_shares = {} groups.each do |group_index, shares| member_threshold = shares.first.member_threshold raise ArgumentError, "Wrong number of mnemonics. Threshold is #{member_threshold}, but share count is #{shares.length}" if shares.length < member_threshold 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| raise ArgumentError, 'Invalid set of shares. All shares in a group must have the same member threshold.' unless member_threshold == share.member_threshold raise ArgumentError, 'Invalid set of shares. All share values must have the same length.' unless value_length == share.value.length x_coordinates << share.member_index end x_coordinates.uniq! raise ArgumentError, 'Invalid set of shares. Share indices must be unique.' unless x_coordinates.size == shares.size 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 raise ArgumentError, "Wrong number of mnemonics. Group threshold is #{group_threshold}, but share count is #{group_shares.length}" if group_shares.length < group_threshold 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[Bitcoin::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 = Bitcoin::SLIP39::SSS.setup_shares(group_threshold: 2, groups: groups, secret: ‘secret with hex format’, passphrase: ‘xxx’) return 4 group array of Bitcoin::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”]
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 |
# File 'lib/bitcoin/slip39/sss.rb', line 36 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 raise ArgumentError, "The length of the master secret (#{secret.htb.bytesize} bytes) must be at least #{MIN_STRENGTH_BITS / 8} bytes." if (secret.htb.bytesize * 8) < MIN_STRENGTH_BITS raise ArgumentError, 'The length of the master secret in bytes must be an even number.' unless secret.bytesize.even? raise ArgumentError, 'The passphrase must contain only printable ASCII characters (code points 32-126).' unless passphrase.ascii_only? raise ArgumentError, "The requested group threshold (#{group_threshold}) must not exceed the number of groups (#{groups.length})." if group_threshold > groups.length groups.each do |threshold, count| raise ArgumentError, 'Group threshold must be greater than 0.' if threshold.nil? || threshold < 1 raise ArgumentError, "The requested member threshold (#{threshold}) must not exceed the number of share (#{count})." if threshold > count raise ArgumentError, "Creating multiple member shares with member threshold 1 is not allowed. Use 1-of-1 member sharing instead." if threshold == 1 && count > 1 end id = SecureRandom.random_number(32767) # 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 = Bitcoin::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 |