tss 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ module TSS
2
+ class Error < RuntimeError; end
3
+ class ArgumentError < Error; end
4
+ end
@@ -0,0 +1,55 @@
1
+ module TSS
2
+ class Hasher
3
+ HASHES = { NONE: { code: 0, bytesize: 0, hasher: nil },
4
+ SHA1: { code: 1, bytesize: 20, hasher: Digest::SHA1 },
5
+ SHA256: { code: 2, bytesize: 32, hasher: Digest::SHA256 }
6
+ }.freeze
7
+
8
+ def self.key_from_code(code)
9
+ return nil unless Hasher.codes.include?(code)
10
+ HASHES.each do |k, v|
11
+ return k if v[:code] == code
12
+ end
13
+ end
14
+
15
+ def self.code(hash_key)
16
+ HASHES[hash_key.upcase.to_sym][:code]
17
+ end
18
+
19
+ # All valid hash codes, including NONE
20
+ def self.codes
21
+ HASHES.collect do |_k, v|
22
+ v[:code]
23
+ end
24
+ end
25
+
26
+ # All valid hash codes that actually do hashing
27
+ def self.codes_without_none
28
+ HASHES.collect do |_k, v|
29
+ v[:code] if v[:code] > 0
30
+ end.compact
31
+ end
32
+
33
+ def self.bytesize(hash_key)
34
+ HASHES[hash_key.upcase.to_sym][:bytesize]
35
+ end
36
+
37
+ def self.hex_string(hash_key, str)
38
+ hash_key = hash_key.upcase.to_sym
39
+ return '' if hash_key == :NONE
40
+ HASHES[hash_key][:hasher].send(:hexdigest, str)
41
+ end
42
+
43
+ def self.byte_string(hash_key, str)
44
+ hash_key = hash_key.upcase.to_sym
45
+ return '' if hash_key == :NONE
46
+ HASHES[hash_key][:hasher].send(:digest, str)
47
+ end
48
+
49
+ def self.byte_array(hash_key, str)
50
+ hash_key = hash_key.upcase.to_sym
51
+ return [] if hash_key == :NONE
52
+ HASHES[hash_key][:hasher].send(:digest, str).unpack('C*')
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,190 @@
1
+ module TSS
2
+ class Splitter < Dry::Types::Struct
3
+ include Util
4
+
5
+ SHARE_HEADER_STRUCT = BinaryStruct.new([
6
+ 'a16', :identifier, # String, 16 Bytes, arbitrary binary string (null padded, count is width)
7
+ 'C', :hash_id,
8
+ 'C', :threshold,
9
+ 'n', :share_len
10
+ ])
11
+
12
+ # dry-types
13
+ constructor_type(:schema)
14
+
15
+ attribute :secret, Types::Strict::String
16
+ .constrained(min_size: 1)
17
+
18
+ attribute :threshold, Types::Coercible::Int
19
+ .constrained(gteq: 1)
20
+ .constrained(lteq: 255)
21
+ .default(3)
22
+
23
+ attribute :num_shares, Types::Coercible::Int
24
+ .constrained(gteq: 1)
25
+ .constrained(lteq: 255)
26
+ .default(5)
27
+
28
+ attribute :identifier, Types::Strict::String
29
+ .constrained(min_size: 0)
30
+ .constrained(max_size: 16)
31
+ .default { SecureRandom.hex(8) }
32
+ .constrained(format: /^[a-zA-Z0-9\-\_\.]*$/i) # 0 or more of these chars
33
+
34
+ attribute :hash_alg, Types::Strict::String.enum('NONE', 'SHA1', 'SHA256')
35
+ .default('SHA256')
36
+
37
+ attribute :format, Types::Strict::String.enum('binary', 'human')
38
+ .default('binary')
39
+
40
+ attribute :pad_blocksize, Types::Coercible::Int
41
+ .constrained(gteq: 0)
42
+ .constrained(lteq: 255)
43
+ .default(0)
44
+
45
+ # The `split` method takes a Hash of arguments. The following hash key args
46
+ # may be passed. Only `secret:` is required and the rest will be set to
47
+ # reasonable and secure defaults if unset. All args will be validated for
48
+ # correct type and values on object instantiation.
49
+ #
50
+ # `secret:` (required) takes a String (UTF-8 or US-ASCII encoding) with a
51
+ # length between 1..65_534
52
+ #
53
+ # `threshold:` The number of shares (M) that will be required to recombine the
54
+ # secret. Must be a value between 1..255 inclusive. Defaults to
55
+ # a threshold of 3 shares.
56
+ #
57
+ # `num_shares:` The total number of shares (N) that will be created. Must be
58
+ # a value between the `threshold` value (M) and 255 inclusive.
59
+ # The upper limit is particular to the TSS algorithm used.
60
+ # Defaults to generating 5 total shares.
61
+ #
62
+ # `identifier:` A 0-16 bytes String limited to the characters 0-9, a-z, A-Z,
63
+ # the dash (-), the underscore (_), and the period (.). The identifier will
64
+ # be embedded in each the binary header of each share and should not reveal
65
+ # anything about the secret. It defaults to the value of `SecureRandom.hex(8)`
66
+ # which returns a random 16 Byte string which represents a Base10 decimal
67
+ # between 1 and 18446744073709552000.
68
+ #
69
+ # `hash_alg:` The one-way hash algorithm that will be used to verify the
70
+ # secret returned by a later recombine operation is identical to what was
71
+ # split. This value will be concatenated with the secret prior to splitting.
72
+ # The valid hash algorithm values are `NONE`, `SHA1`, and `SHA256`. Defaults
73
+ # to `SHA256`. The use of `NONE` is discouraged as it does not allow those
74
+ # who are recombining the shares to verify if they have in fact recovered
75
+ # the correct secret.
76
+ #
77
+ # `pad_blocksize:` An integer representing the nearest multiple of Bytes
78
+ # to left pad the secret to. Defaults to not adding any padding (0). Padding
79
+ # is done with the "\u001F" character (decimal 31 in a Byte Array).
80
+ # Since TSS share data (minus the header) is essentially the same size as the
81
+ # original secret, padding smaller secrets may help mask the size of the
82
+ # contents from an attacker. Padding is not part of the RTSS spec so other
83
+ # TSS clients won't strip off the padding and may not validate correctly.
84
+ # If you need this interoperability you should probably pad the secret
85
+ # yourself prior to splitting it and leave the default zero-length pad in
86
+ # place. You would also need to manually remove the padding you added after
87
+ # the share is recombined.
88
+ #
89
+ # Calling `split` *must* return an Array of formatted shares or raise one of
90
+ # `TSS::Error` or `TSS::ArgumentError` exceptions if anything has gone wrong.
91
+ #
92
+ def split
93
+ secret_has_acceptable_encoding(secret)
94
+ secret_does_not_begin_with_padding_char(secret)
95
+ num_shares_not_less_than_threshold(threshold, num_shares)
96
+
97
+ # RTSS : Combine the secret with a hash digest before splitting. On recombine
98
+ # the two will be separated again and the hash used to validate the
99
+ # correct secret was returned. secret || hash(secret). You can also
100
+ # optionally pad the secret first.
101
+ padded_secret = Util.left_pad(pad_blocksize, secret)
102
+ hashed_secret = Hasher.byte_array(hash_alg, secret)
103
+ secret_bytes = Util.utf8_to_bytes(padded_secret) + hashed_secret
104
+
105
+ secret_bytes_is_smaller_than_max_size(secret_bytes)
106
+
107
+ # For each share, a distinct Share Index is generated. Each Share
108
+ # Index is an octet other than the all-zero octet. All of the Share
109
+ # Indexes used during a share generation process MUST be distinct.
110
+ # Each share is initialized to the Share Index associated with that
111
+ # share.
112
+ shares = []
113
+ (1..num_shares).each { |n| shares << [n] }
114
+
115
+ # For each octet of the secret, the following steps are performed.
116
+ #
117
+ # An array A of M octets is created, in which the array element A[0]
118
+ # contains the octet of the secret, and the array elements A[1],
119
+ # ..., A[M-1] contain octets that are selected independently and
120
+ # uniformly at random.
121
+ #
122
+ # For each share, the value of f(X,A) is
123
+ # computed, where X is the Share Index of the share, and the
124
+ # resulting octet is appended to the share.
125
+ #
126
+ # After the procedure is done, each share contains one more octet than
127
+ # does the secret. The share format can be illustrated as
128
+ #
129
+ # +---------+---------+---------+---------+---------+
130
+ # | X | f(X,A) | f(X,B) | f(X,C) | ... |
131
+ # +---------+---------+---------+---------+---------+
132
+ #
133
+ # where X is the Share Index of the share, and A, B, and C are arrays
134
+ # of M octets; A[0] is equal to the first octet of the secret, B[0] is
135
+ # equal to the second octet of the secret, and so on.
136
+ #
137
+ secret_bytes.each do |byte|
138
+ # Unpack random Byte String into Byte Array of 8 bit unsigned Integers
139
+ r = SecureRandom.random_bytes(threshold - 1).unpack('C*')
140
+
141
+ # Build each share one byte at a time for each byte of the secret.
142
+ shares.map! { |s| s << Util.f(s[0], [byte] + r) }
143
+ end
144
+
145
+ # build up a common binary struct header for all shares
146
+ header = share_header(identifier, hash_alg, threshold, shares.first.length)
147
+
148
+ # create each binary share and return it.
149
+ shares.map! do |s|
150
+ binary = (header + s.pack('C*')).force_encoding('ASCII-8BIT')
151
+ # join with URL safe '~'
152
+ human = ['tss', 'v1', identifier, threshold, Base64.urlsafe_encode64(binary)].join('~')
153
+ format == 'binary' ? binary : human
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ def secret_has_acceptable_encoding(secret)
160
+ unless secret.encoding.name == 'UTF-8' || secret.encoding.name == 'US-ASCII'
161
+ raise TSS::ArgumentError, "invalid secret, must be a UTF-8 or US-ASCII encoded String not '#{secret.encoding.name}'"
162
+ end
163
+ end
164
+
165
+ def secret_does_not_begin_with_padding_char(secret)
166
+ if secret.slice(0) == "\u001F"
167
+ raise TSS::ArgumentError, 'invalid secret, first byte of secret is the reserved left-pad character (\u001F)'
168
+ end
169
+ end
170
+
171
+ def num_shares_not_less_than_threshold(threshold, num_shares)
172
+ if num_shares < threshold
173
+ raise TSS::ArgumentError, "invalid num_shares, must be >= threshold (#{threshold})"
174
+ end
175
+ end
176
+
177
+ def secret_bytes_is_smaller_than_max_size(secret_bytes)
178
+ if secret_bytes.size >= 65_535
179
+ raise TSS::ArgumentError, 'invalid secret, combined padded secret and hash are too large'
180
+ end
181
+ end
182
+
183
+ def share_header(identifier, hash_alg, threshold, share_len)
184
+ SHARE_HEADER_STRUCT.encode(identifier: identifier,
185
+ hash_id: Hasher.code(hash_alg),
186
+ threshold: threshold,
187
+ share_len: share_len)
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,15 @@
1
+ module TSS
2
+ def self.split(args)
3
+ unless args.is_a?(Hash) && args.key?(:secret)
4
+ raise TSS::ArgumentError, 'TSS.split takes a Hash of arguments with at least a :secret key'
5
+ end
6
+ TSS::Splitter.new(args).split
7
+ end
8
+
9
+ def self.combine(args)
10
+ unless args.is_a?(Hash) && args.key?(:shares)
11
+ raise TSS::ArgumentError, 'TSS.combine takes a Hash of arguments with at least a :shares key'
12
+ end
13
+ TSS::Combiner.new(args).combine
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ # dry-types
2
+ module Types
3
+ include Dry::Types.module
4
+ end
@@ -0,0 +1,266 @@
1
+ # Common utility and math functions
2
+ module TSS
3
+ module Util
4
+ # The EXP table. The elements are to be read from top to
5
+ # bottom and left to right. For example, EXP[0] is 0x01, EXP[8] is
6
+ # 0x1a, and so on. Note that the EXP[255] entry is present only as a
7
+ # placeholder, and is not actually used in any computation.
8
+ EXP = [0x01, 0x03, 0x05, 0x0f, 0x11, 0x33, 0x55, 0xff,
9
+ 0x1a, 0x2e, 0x72, 0x96, 0xa1, 0xf8, 0x13, 0x35,
10
+ 0x5f, 0xe1, 0x38, 0x48, 0xd8, 0x73, 0x95, 0xa4,
11
+ 0xf7, 0x02, 0x06, 0x0a, 0x1e, 0x22, 0x66, 0xaa,
12
+ 0xe5, 0x34, 0x5c, 0xe4, 0x37, 0x59, 0xeb, 0x26,
13
+ 0x6a, 0xbe, 0xd9, 0x70, 0x90, 0xab, 0xe6, 0x31,
14
+ 0x53, 0xf5, 0x04, 0x0c, 0x14, 0x3c, 0x44, 0xcc,
15
+ 0x4f, 0xd1, 0x68, 0xb8, 0xd3, 0x6e, 0xb2, 0xcd,
16
+ 0x4c, 0xd4, 0x67, 0xa9, 0xe0, 0x3b, 0x4d, 0xd7,
17
+ 0x62, 0xa6, 0xf1, 0x08, 0x18, 0x28, 0x78, 0x88,
18
+ 0x83, 0x9e, 0xb9, 0xd0, 0x6b, 0xbd, 0xdc, 0x7f,
19
+ 0x81, 0x98, 0xb3, 0xce, 0x49, 0xdb, 0x76, 0x9a,
20
+ 0xb5, 0xc4, 0x57, 0xf9, 0x10, 0x30, 0x50, 0xf0,
21
+ 0x0b, 0x1d, 0x27, 0x69, 0xbb, 0xd6, 0x61, 0xa3,
22
+ 0xfe, 0x19, 0x2b, 0x7d, 0x87, 0x92, 0xad, 0xec,
23
+ 0x2f, 0x71, 0x93, 0xae, 0xe9, 0x20, 0x60, 0xa0,
24
+ 0xfb, 0x16, 0x3a, 0x4e, 0xd2, 0x6d, 0xb7, 0xc2,
25
+ 0x5d, 0xe7, 0x32, 0x56, 0xfa, 0x15, 0x3f, 0x41,
26
+ 0xc3, 0x5e, 0xe2, 0x3d, 0x47, 0xc9, 0x40, 0xc0,
27
+ 0x5b, 0xed, 0x2c, 0x74, 0x9c, 0xbf, 0xda, 0x75,
28
+ 0x9f, 0xba, 0xd5, 0x64, 0xac, 0xef, 0x2a, 0x7e,
29
+ 0x82, 0x9d, 0xbc, 0xdf, 0x7a, 0x8e, 0x89, 0x80,
30
+ 0x9b, 0xb6, 0xc1, 0x58, 0xe8, 0x23, 0x65, 0xaf,
31
+ 0xea, 0x25, 0x6f, 0xb1, 0xc8, 0x43, 0xc5, 0x54,
32
+ 0xfc, 0x1f, 0x21, 0x63, 0xa5, 0xf4, 0x07, 0x09,
33
+ 0x1b, 0x2d, 0x77, 0x99, 0xb0, 0xcb, 0x46, 0xca,
34
+ 0x45, 0xcf, 0x4a, 0xde, 0x79, 0x8b, 0x86, 0x91,
35
+ 0xa8, 0xe3, 0x3e, 0x42, 0xc6, 0x51, 0xf3, 0x0e,
36
+ 0x12, 0x36, 0x5a, 0xee, 0x29, 0x7b, 0x8d, 0x8c,
37
+ 0x8f, 0x8a, 0x85, 0x94, 0xa7, 0xf2, 0x0d, 0x17,
38
+ 0x39, 0x4b, 0xdd, 0x7c, 0x84, 0x97, 0xa2, 0xfd,
39
+ 0x1c, 0x24, 0x6c, 0xb4, 0xc7, 0x52, 0xf6, 0x00].freeze
40
+
41
+ # The LOG table. The elements are to be read from top to
42
+ # bottom and left to right. For example, LOG[1] is 0, LOG[8] is 75,
43
+ # and so on. Note that the LOG[0] entry is present only as a
44
+ # placeholder, and is not actually used in any computation.
45
+ LOG = [0, 0, 25, 1, 50, 2, 26, 198,
46
+ 75, 199, 27, 104, 51, 238, 223, 3,
47
+ 100, 4, 224, 14, 52, 141, 129, 239,
48
+ 76, 113, 8, 200, 248, 105, 28, 193,
49
+ 125, 194, 29, 181, 249, 185, 39, 106,
50
+ 77, 228, 166, 114, 154, 201, 9, 120,
51
+ 101, 47, 138, 5, 33, 15, 225, 36,
52
+ 18, 240, 130, 69, 53, 147, 218, 142,
53
+ 150, 143, 219, 189, 54, 208, 206, 148,
54
+ 19, 92, 210, 241, 64, 70, 131, 56,
55
+ 102, 221, 253, 48, 191, 6, 139, 98,
56
+ 179, 37, 226, 152, 34, 136, 145, 16,
57
+ 126, 110, 72, 195, 163, 182, 30, 66,
58
+ 58, 107, 40, 84, 250, 133, 61, 186,
59
+ 43, 121, 10, 21, 155, 159, 94, 202,
60
+ 78, 212, 172, 229, 243, 115, 167, 87,
61
+ 175, 88, 168, 80, 244, 234, 214, 116,
62
+ 79, 174, 233, 213, 231, 230, 173, 232,
63
+ 44, 215, 117, 122, 235, 22, 11, 245,
64
+ 89, 203, 95, 176, 156, 169, 81, 160,
65
+ 127, 12, 246, 111, 23, 196, 73, 236,
66
+ 216, 67, 31, 45, 164, 118, 123, 183,
67
+ 204, 187, 62, 90, 251, 96, 177, 134,
68
+ 59, 82, 161, 108, 170, 85, 41, 157,
69
+ 151, 178, 135, 144, 97, 190, 220, 252,
70
+ 188, 149, 207, 205, 55, 63, 91, 209,
71
+ 83, 57, 132, 60, 65, 162, 109, 71,
72
+ 20, 42, 158, 93, 86, 242, 211, 171,
73
+ 68, 17, 146, 217, 35, 32, 46, 137,
74
+ 180, 124, 184, 38, 119, 153, 227, 165,
75
+ 103, 74, 237, 222, 197, 49, 254, 24,
76
+ 13, 99, 140, 128, 192, 247, 112, 7].freeze
77
+
78
+ # The addition operation returns the Bitwise
79
+ # Exclusive OR (XOR) of its operands.
80
+ def self.gf256_add(a, b)
81
+ a ^ b
82
+ end
83
+
84
+ # The subtraction operation is identical, because the field has
85
+ # characteristic two.
86
+ def self.gf256_sub(a, b)
87
+ gf256_add(a, b)
88
+ end
89
+
90
+ # The multiplication operation takes two elements X and Y as input and
91
+ # proceeds as follows. If either X or Y is equal to 0x00, then the
92
+ # operation returns 0x00. Otherwise, the value EXP[ (LOG[X] + LOG[Y])
93
+ # modulo 255] is returned.
94
+ def self.gf256_mul(x, y)
95
+ return 0 if x == 0 || y == 0
96
+ EXP[(LOG[x] + LOG[y]) % 255]
97
+ end
98
+
99
+ # The division operation takes a dividend X and a divisor Y as input
100
+ # and computes X divided by Y as follows. If X is equal to 0x00, then
101
+ # the operation returns 0x00. If Y is equal to 0x00, then the input is
102
+ # invalid, and an error condition occurs. Otherwise, the value
103
+ # EXP[(LOG[X] - LOG[Y]) modulo 255] is returned.
104
+ def self.gf256_div(x, y)
105
+ return 0 if x == 0
106
+ raise TSS::Error, 'divide by zero' if y == 0
107
+ EXP[(LOG[x] - LOG[y]) % 255]
108
+ end
109
+
110
+ # Share generation Functions
111
+ ############################
112
+
113
+ # We first define how to share a single octet.
114
+ #
115
+ # The function f takes as input a single octet X that is not equal to
116
+ # 0x00, and an array A of M octets, and returns a single octet. It is
117
+ # defined as:
118
+ #
119
+ # f(X, A) = GF_SUM A[i] (*) X^i
120
+ # i=0,M-1
121
+ #
122
+ # Because the GF_SUM summation takes place over GF(256), each addition
123
+ # uses the exclusive-or operation, and not integer addition. Note that
124
+ # the successive values of X^i used in the computation of the function
125
+ # f can be computed by multiplying a value by X once for each term in
126
+ # the summation.
127
+ def self.f(x, bytes)
128
+ raise TSS::Error, 'invalid share index value, cannot be 0' if x == 0
129
+ y = 0
130
+ x_i = 1
131
+
132
+ bytes.each do |b|
133
+ y = gf256_add(y, gf256_mul(b, x_i))
134
+ x_i = gf256_mul(x_i, x)
135
+ end
136
+
137
+ y
138
+ end
139
+
140
+ # Secret Reconstruction Functions
141
+ # ###############################
142
+
143
+ # We define the function L_i (for i from 0 to M-1, inclusive) that
144
+ # takes as input an array U of M pairwise distinct octets, and is
145
+ # defined as
146
+ #
147
+ # U[j]
148
+ # L_i(U) = GF_PRODUCT -------------
149
+ # j=0,M-1, j!=i U[j] (+) U[i]
150
+ #
151
+ # Here the product runs over all of the values of j from 0 to M-1,
152
+ # excluding the value i. (This function is equal to ith Lagrange
153
+ # function, evaluated at zero.) Note that the denominator in the above
154
+ # expression is never equal to zero because U[i] is not equal to U[j]
155
+ # whenever i is not equal to j.
156
+ #
157
+ def self.basis_poly(i, u)
158
+ prod = 1
159
+
160
+ (0..(u.length - 1)).each do |j|
161
+ next if i == j
162
+ prod = gf256_mul(prod, gf256_div(u[j], gf256_add(u[j], u[i])))
163
+ end
164
+
165
+ prod
166
+ end
167
+
168
+ # We denote the interpolation function as I. This function takes as
169
+ # input two arrays U and V, each consisting of M octets, and returns a
170
+ # single octet; it is defined as:
171
+ #
172
+ # I(U, V) = GF_SUM L_i(U) (*) V[i].
173
+ # i=0,M-1
174
+ #
175
+ def self.lagrange_interpolation(u, v)
176
+ sum = 0
177
+
178
+ (0..(v.length - 1)).each do |i|
179
+ sum = gf256_add(sum, gf256_mul(basis_poly(i, u), v[i]))
180
+ end
181
+
182
+ sum
183
+ end
184
+
185
+ # Conversion Functions
186
+ ######################
187
+
188
+ # String to Byte Array
189
+ def self.utf8_to_bytes(str)
190
+ str.bytes.to_a
191
+ end
192
+
193
+ # Byte Array to String
194
+ def self.bytes_to_utf8(bytes)
195
+ bytes.pack('C*').force_encoding('utf-8')
196
+ end
197
+
198
+ def self.bytes_to_hex(bytes)
199
+ hex = ''
200
+ bytes.each { |b| hex += sprintf('%02x', b).upcase }
201
+ hex
202
+ end
203
+
204
+ def self.hex_to_bytes(str)
205
+ # clone so we don't destroy the original string passed in by slicing it.
206
+ strc = str.clone
207
+ bytes = []
208
+ len = strc.length
209
+ raise TSS::Error, 'invalid hex value, cannot be an odd length' if len.odd?
210
+ # slice off two hex chars at a time and convert them to an Integer Byte.
211
+ (len / 2).times { bytes << strc.slice!(0, 2).hex }
212
+ bytes
213
+ end
214
+
215
+ def self.hex_to_utf8(hex)
216
+ bytes_to_utf8(hex_to_bytes(hex))
217
+ end
218
+
219
+ def self.utf8_to_hex(str)
220
+ bytes_to_hex(utf8_to_bytes(str))
221
+ end
222
+
223
+ # String Helpers
224
+
225
+ def self.left_pad(byte_multiple, input_string, pad_char = "\u001F")
226
+ return input_string if byte_multiple == 0
227
+ pad_length = byte_multiple - (input_string.length % byte_multiple)
228
+ return input_string if pad_length == byte_multiple
229
+ (pad_char * pad_length) + input_string
230
+ end
231
+
232
+ # Binary Header Helpers
233
+
234
+ def self.extract_share_header(share)
235
+ h = Splitter::SHARE_HEADER_STRUCT.decode(share)
236
+ h[:identifier] = h[:identifier].delete("\x00")
237
+ return h
238
+ end
239
+
240
+ # Math Helpers
241
+
242
+ def self.factorial(n)
243
+ (1..n).reduce(:*) || 1
244
+ end
245
+
246
+ # Calculate the number of combinations possible
247
+ # for a given number of shares and threshold.
248
+ #
249
+ # See : http://www.wolframalpha.com/input/?i=20+choose+5
250
+ # See : http://www.mathsisfun.com/combinatorics/combinations-permutations-calculator.html (Set balls, 20, 5, no, no) == 15504
251
+ # See : http://www.mathsisfun.com/combinatorics/combinations-permutations.html
252
+ # See : https://jdanger.com/calculating-factorials-in-ruby.html
253
+ # See : http://chriscontinanza.com/2010/10/29/Array.html
254
+ # See : http://stackoverflow.com/questions/2434503/ruby-factorial-function
255
+ #
256
+ # n is the total number of shares
257
+ # r is the threshold number of shares
258
+ def self.calc_combinations(n, r)
259
+ factorial(n) / (factorial(r) * factorial(n - r))
260
+ end
261
+
262
+ def self.int_commas(n, delimiter = ',')
263
+ n.to_s.reverse.gsub(%r{([0-9]{3}(?=([0-9])))}, "\\1#{delimiter}").reverse
264
+ end
265
+ end
266
+ end