extended-shamir-secret-sharing 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f57b4d7f5a831922c68dc6634e270759cae032494f6666a8fb2c8bfa81c62270
4
+ data.tar.gz: bfcb8e736a50524d75be8ac0eac525a197749fc39b09a610698912495f0dd167
5
+ SHA512:
6
+ metadata.gz: 679d1e270c491758465d0c07201896ab0715bd8e55e420b4cbb7ddaf9f1e68044b71d15a1ea2985af236f58fe03ed1e290b75f72a9b58c8e99dccc17ca410796
7
+ data.tar.gz: 2ef5841a83711e0ab3df32e853733f7ff2da59dc708e7a72632b5697568884d0814b36e0e363fa8fe29e4a12f14646cfdfcf9989f1bc1ce84345af887d27cbda
data/README.rdoc ADDED
@@ -0,0 +1,75 @@
1
+ = shamir-secret-sharing
2
+
3
+ This is a ruby library for Shamir's Secret Sharing. ( http://en.wikipedia.org/wiki/Shamir's_Secret_Sharing )
4
+
5
+ == Compatible with...
6
+
7
+ * ruby 1.9.3
8
+ * ruby 2.0.0
9
+
10
+
11
+ == Installation
12
+
13
+ We assume you already have a ruby 1.9 or 2.0 compatible interpreter and rubygems environment.
14
+
15
+ git clone https://github.com/lian/shamir-secret-sharing.git; cd shamir-secret-sharing
16
+ irb -Ilib -rshamir-secret-sharing
17
+
18
+ If you want to have it available system-wide, just build the gem and install it:
19
+
20
+ gem build shamir-secret-sharing.gemspec && gem install shamir-secret-sharing-*.gem
21
+
22
+ == Tests
23
+
24
+ The tests can be run with
25
+
26
+ rake
27
+
28
+ If you make changes to the code or add functionality, please also add tests.
29
+
30
+ = Example
31
+
32
+ == Split and Combine
33
+
34
+ # split a secret into 4 shares. 3 of them are needed to combine the secret again.
35
+ shares = ShamirSecretSharing::Base58.split(secret="hello", available=4, needed=3)
36
+ shares #=> ["FPGPdS98vRUCy9", "VmTjT28nES7Pck", "k9eRjVVpWP7uzj", "zXqDJNRpPttPjR"]
37
+
38
+ # combine the secret again using at least 3 shares
39
+ ShamirSecretSharing::Base58.combine( ["VmTjT28nES7Pck", "zXqDJNRpPttPjR", "k9eRjVVpWP7uzj"] )
40
+ #=> "hello"
41
+
42
+ # not enough shares
43
+ ShamirSecretSharing::Base58.combine( ["VmTjT28nES7Pck", "zXqDJNRpPttPjR"] )
44
+ #=> false
45
+
46
+ # wrong shares
47
+ ShamirSecretSharing::Base58.combine( ["VmTjT28nES7Pck", "zXqDJNRpPttPjR", "some-wrong-share"] )
48
+ #=> false
49
+
50
+ == Encypt and Decrypt
51
+
52
+ Split and combine only work with a secret length below 512 bytes and the resulting share lengths are equivalent to the secret length. That means a 128 byte secret has a very long (128 byte) share length.
53
+ Encrypt and decrypt are used to encrypt a large piece of data while keeping the share lengths to a fixed and reasonable size. It does that by AES-256-CBC encrypting the data with a random key and only then splitting that fixed size key into shares.
54
+
55
+ # encrypt the data and create 4 shares. Later 3 shares and the encrypted data are needed to decrypt it again. This example uses a 96 bit random key for encryption.
56
+ shares, encrypted = ShamirSecretSharing::Base58.encrypt(data="Lorem ipsum dolor sit amet, consectetur adipiscing elit.", 4, 3, 96)
57
+ shares #=> ["3QZNRFYZHKUpnv96cE6eVwPP", "5p611mjtxXBgPDx1m2SRwCKr", "8DcdGMD9i3kWGrYEVGtQLSWA", "Ad9NLPDVSnHfbqVhnmNn8YK4"]
58
+ encrypted #=> "4PHcPwzzk48EjmusQz27t3X1rS7hAi9kwKgZJ7UMDR99rJgi19aHM8e7syh6uVrJ6HbcbgRmEUcR7hmkDvrYaJXH"
59
+
60
+ # combine the shares and decrypt the encrypted data
61
+ ShamirSecretSharing::Base58.decrypt(["3QZNRFYZHKUpnv96cE6eVwPP", "8DcdGMD9i3kWGrYEVGtQLSWA", "Ad9NLPDVSnHfbqVhnmNn8YK4"], "4PHcPwzzk48EjmusQz27t3X1rS7hAi9kwKgZJ7UMDR99rJgi19aHM8e7syh6uVrJ6HbcbgRmEUcR7hmkDvrYaJXH")
62
+ #=> "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
63
+
64
+ # not enough shares
65
+ ShamirSecretSharing::Base58.decrypt(["3QZNRFYZHKUpnv96cE6eVwPP", "8DcdGMD9i3kWGrYEVGtQLSWA"], "4PHcPwzzk48EjmusQz27t3X1rS7hAi9kwKgZJ7UMDR99rJgi19aHM8e7syh6uVrJ6HbcbgRmEUcR7hmkDvrYaJXH")
66
+ #=> false
67
+
68
+ # wrong shares
69
+ ShamirSecretSharing::Base58.decrypt(["3QZNRFYZHKUpnv96cE6eVwPP", "8DcdGMD9i3kWGrYEVGtQLSWA", "some-wrong-share"], "4PHcPwzzk48EjmusQz27t3X1rS7hAi9kwKgZJ7UMDR99rJgi19aHM8e7syh6uVrJ6HbcbgRmEUcR7hmkDvrYaJXH")
70
+ #=> false
71
+
72
+ # wrong encrypted data
73
+ ShamirSecretSharing::Base58.decrypt(["3QZNRFYZHKUpnv96cE6eVwPP", "8DcdGMD9i3kWGrYEVGtQLSWA", "Ad9NLPDVSnHfbqVhnmNn8YK4"], "wrong-encrypted-data")
74
+ #=> false
75
+
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ begin
2
+ require 'bundler/gem_tasks'
3
+ rescue LoadError
4
+ end
5
+
6
+ RUBY = 'ruby' unless defined?(RUBY)
7
+
8
+ task :default => :tests
9
+
10
+ # test runner [0/61]
11
+ desc 'Run all bacon specs with pretty output'
12
+ task :tests do
13
+ sh RUBY, 'lib/shamir-secret-sharing.rb'
14
+ end
@@ -0,0 +1,382 @@
1
+ require 'openssl'
2
+ require 'digest/sha1'
3
+
4
+ class ShamirSecretSharing
5
+ VERSION = '0.1.0'
6
+
7
+ def self.prime
8
+ @prime ||= OpenSSL::BN.new('90298080309315418557060368684387169711767062077820047328483946136026783868610231658004529474715593165227045823796809053316421038435080529340276649188231489578413082915429082171276815832832032596987468328852992416302194653059769701301855112417280173119831084529497140275721119033981819754004876481947376857582541086674191309050669339147047177722482742095623467804455161314645495512219242738453400110821615485595951493732348640398317903244696668482247005070591932639428562532268906727083775005769341161737441206890423884947292116895278573352752642105943524899087988610386051326114173719939938357597470028921187928607042904261588275872786996786421015124382796026032641093550978376530319328301152270163150961443743005805156487456518852641096288933759632928527097707771705108593435811210969899009718025806082982491815839959394862199132189412760797205753603085181613915340914509742283721398758884882857717577570891251312844452052745163947127230739899990249592181831046734568185882323553723058313378711522655212295569618792861569553260909021907243424735691066494898818760523978370666490738958317270115402545013981689071761804217481237129399611746232468395365054918893973245564093211165225302041175079585458888691055939901734787886063765286977492815858172765414297704166310822063655804295492269403809136990123946872416301581476512804600437921871062337466733019509934199383005641657881387476608548204828441502666519703299105349809939561047193583345522023506184856674255042110783737263099438319847599648008766818797429771443705028772651586692324064007017187902934084171715785037116043811965267781489643030445117031408644673321057391176558036893857789298122893483373295134914414310225512185182855428723091437135258390607647079002628450028353308872465259623761042459298605601867079931599883341211337021548912929591032567679109586055855175707483614097894547800956575849455311488677165455115982505416964026617179448438285990610826073646616767534962613889972341177404511628350260670301108812261943592601817837534438293861144285248114026494593413438072008145151195558510253887426294107818889183153563906542962676822500733556368475553257951001559608026694402026083311647661603527141723939606662260781541702812518282966559303942883569550595968567624849771779902009005634939910112687785085261271077120292302150188079499520405255241734961072150325600567125813015450184377437377118236641143759546112499433705368329863240052878614034365965400707301060867667467921246288162184422787472727180885468250036866234631784065140572659371113921558324588343588649262665138554305162189719942919492935163152228273161856121456840243572074505539715086267815541055014575779183357826164049356330040472884738976674077057074492894659512026336932025359399208067756553441936121229276677493655969096449480584011430005309053544504304875804843205273883837061928391103734105527700616760751820474231999825032847945883064932406572688251330712502565439000257427471703412233595773324058194221908861303293341560475980198905863935850405064924144381710080649869192412763511460084172591012489581496077540366143109773048609220052015662328678967053922114059228108612468779055218472293622854582726541691332097684877156682496500546767264347154842487600035258894233822255723444907740808744398357482803391100715574412706233117206636112256824018335300077628867404705796199494183312134381338806813000075497747803240234241994282044442903373163715122536646305967943100639503123443493819526136342147706606394282112583884721431928417322993021536731405231184306729967529111955045252260102353296822531279058046816735479320770852433850566679904058973845613063366857995248536709558547947005487959070944758243881165013091044353829893545161327798124166213688912777335160265951849666987641506720154010364630736090984018386925406284136947816502665237274359499520079866122535658013617866311362943581086846194924950315777735026525882487543617430916255273833298323517701745687510934619838104831680724070326313574890013327805200827123185767256005905063632000378392141574495904804421309409555139440835558113898451033592526676935989162452243800981511319774846636555021940628714873179898475129018414822388672074777887836816908866710716135238849926550730803704711315750950458422935489673267976222635275704248820229824996510107205198663605380240651729687971710957296407610255077817146837349063148581060337597269343984345091912700819071375585519821604434603431994464701457318775403702846559037160195794889488908029233286151225725905398606350109962941753161584200144074269884326067022889478112825554120260952696169252329753222034898637234313852548016829410522468275347612080526487056946419000642199642660133237695390182834252238828073436204843551107043444186518737285718064454348783725196174064580015729871611222847895929389800649019254831014513113431655578925679204506338865404876491637286206819918930558503201366328626510435408373432348415238531403287283959561857892591427352842104554959608115380742631785942302014581426310009539709468373835178808015087703244680357716266673938316659211853843914796941131721320498518038272867994107029017543970812956754144964043650615822951424951757570100497007826364422530866395354057037623197190431202946206475807094227419540419168025621836505136395345095391267796526635539815975433875182503787461610057361924233524420270967631328159401123004381979801026089422686648542947429448884700847284807913378433322255357823885663399560901851919616533557061506731352057344807459911094519333689705182500885752770652880878560576235311015954069515891882699932824200620047914931186958185540784216011367774214867867886436031378064197674396368328035634225912051811470896936074219358317351694009621171355985142146081500737')
9
+ end
10
+
11
+ def self.pack(shares); shares; end
12
+ def self.unpack(shares); shares; end
13
+ def self.encode(string); string; end
14
+ def self.decode(string); string; end
15
+
16
+ def self.smallest_prime_of_bytelength(bytelength)
17
+ n = OpenSSL::BN.new((2**(bytelength*8)+1).to_s)
18
+ loop{ break if n.prime_fasttest?(20); n += 2 }
19
+ n
20
+ end
21
+
22
+ def self.split(secret, available, needed, do_data_checksum=true)
23
+ raise ArgumentError, "needed must be <= available" unless needed <= available
24
+ raise ArgumentError, "needed must be >= 2" unless needed >= 2
25
+ raise ArgumentError, "available must be <= 250" unless available <= 250
26
+
27
+ if do_data_checksum
28
+ checksum = Digest::SHA512.digest(secret)[0...2]
29
+ num_bytes = secret.bytesize+2
30
+ secret = OpenSSL::BN.new((checksum + secret).unpack("H*")[0], 16) rescue OpenSSL::BN.new("0")
31
+ #num_bytes = secret.to_s(0).unpack("N")[0]
32
+ raise ArgumentError, "bytelength of secret must be >= 1" if num_bytes < 3
33
+ raise ArgumentError, "bytelength of secret must be <= 4096" if num_bytes > 4097
34
+ else
35
+ num_bytes = secret.bytesize
36
+ secret = OpenSSL::BN.new(secret.unpack("H*")[0], 16) rescue OpenSSL::BN.new("0") # without checksum
37
+ raise ArgumentError, "bytelength of secret must be >= 1" if num_bytes < 1
38
+ raise ArgumentError, "bytelength of secret must be <= 4096" if num_bytes > 4096
39
+ end
40
+
41
+ coef = [ secret ] + Array.new(needed-1){ OpenSSL::BN.rand(num_bytes * 8) }
42
+
43
+ shares = (1..available).map{|x|
44
+ x = OpenSSL::BN.new(x.to_s)
45
+ y = coef.each_with_index.inject(OpenSSL::BN.new("0")){|acc, (c, idx)|
46
+ acc + c * x.mod_exp(idx, prime)
47
+ } % prime
48
+ [x, num_bytes, y]
49
+ }
50
+ pack(shares)
51
+ end
52
+
53
+ def self.combine(shares, do_raise=false, do_data_checksum=true)
54
+ return false if shares.size < 2
55
+ shares = unpack(shares)
56
+ num_bytes = shares[0][1]
57
+
58
+ secret = shares.inject(OpenSSL::BN.new("0")){|secret,(x,num_bytes,y)|
59
+ l_x = l(x, shares, prime)
60
+ summand = OpenSSL::BN.new(y.to_s).mod_mul(l_x, prime)
61
+ secret = (secret + summand) % prime
62
+ }
63
+ if do_data_checksum
64
+ checksum, secret = [ secret.to_s(16).rjust(num_bytes*2, '0') ].pack("H*").unpack("a2a*")
65
+ checksum == Digest::SHA512.digest(secret)[0...2] ? secret : false
66
+ else
67
+ secret = [ secret.to_s(16).rjust(num_bytes*2, '0') ].pack("H*")
68
+ end
69
+ rescue ShareChecksumError, ShareDecodeError => ex
70
+ raise if do_raise
71
+ false
72
+ end
73
+
74
+ # Part of the Lagrange interpolation.
75
+ # This is l_j(0), i.e. # \prod_{x_j \neq x_i} \frac{-x_i}{x_j - x_i}
76
+ # for more information compare Wikipedia: # http://en.wikipedia.org/wiki/Lagrange_form
77
+ def self.l(current_x, shares, prime)
78
+ shares.select{|x,num_bytes,y| x != current_x }.map{|x,num_bytes,y|
79
+ minus_xi = OpenSSL::BN.new((-x).to_s)
80
+ one_over_xj_minus_xi = OpenSSL::BN.new((current_x - x).to_s).mod_inverse(prime)
81
+ minus_xi.mod_mul(one_over_xj_minus_xi, prime)
82
+ }.inject{|p,f| p.mod_mul(f, prime) }
83
+ end
84
+
85
+ def self.encrypt(data, available, needed, key_bit_length=128)
86
+ key = key_bit_length.is_a?(String) ? key_bit_length : [ OpenSSL::BN.rand(key_bit_length).to_s(16) ].pack("H*")
87
+ c = OpenSSL::Cipher.new('aes-256-cbc').encrypt
88
+ c.key, c.iv = Digest::SHA512.digest(key).unpack("a32a16")
89
+ encrypted = c.update(data) << c.final
90
+ [ split(key, available, needed), encode(encrypted) ]
91
+ end
92
+
93
+ def self.decrypt(shares, encrypted, do_raise=false)
94
+ key = combine(shares, do_raise)
95
+ return false unless key
96
+
97
+ encrypted_decoded = decode(encrypted) rescue nil
98
+ raise ShareDecodeError, "encrypted_data: #{encrypted}" unless encrypted_decoded
99
+
100
+ return false unless encrypted and key
101
+ c = OpenSSL::Cipher.new('aes-256-cbc').decrypt
102
+ c.key, c.iv = Digest::SHA512.digest(key).unpack("a32a16")
103
+ data = c.update(encrypted_decoded) << c.final
104
+ data
105
+ rescue OpenSSL::Cipher::CipherError, ShareDecodeError
106
+ raise if do_raise
107
+ false
108
+ end
109
+
110
+ def self.split_with_sanity_check(secret, available, needed, do_data_checksum=true)
111
+ shares = split(secret, available, needed, do_data_checksum)
112
+ success = true
113
+ needed.upto(available).each{|n| shares.permutation(n).each{|shares| success = false if combine(shares) != secret } }
114
+ (needed-1).downto(2).each{|n| shares.permutation(n).each{|shares| success = false if combine(shares) != false } }
115
+ raise ShareSanityCheckError if success != true
116
+ shares
117
+ rescue ShareSanityCheckError
118
+ retry
119
+ end
120
+
121
+ def self.encrypt_with_sanity_check(data, available, needed, key_bit_length=128)
122
+ shares, encrypted = encrypt(data, available, needed, key_bit_length)
123
+ success = true
124
+ needed.upto(available).each{|n| shares.permutation(n).each{|shares| success = false if decrypt(shares, encrypted) != data } }
125
+ (needed-1).downto(2).each{|n| shares.permutation(n).each{|shares| success = false if decrypt(shares, encrypted) != false } }
126
+ raise ShareSanityCheckError if success != true
127
+ [shares, encrypted]
128
+ rescue ShareSanityCheckError
129
+ retry
130
+ end
131
+
132
+
133
+ class Number < ShamirSecretSharing
134
+ def self.split(secret, available, needed)
135
+ num = OpenSSL::BN.new(secret.to_s)
136
+ raise ArgumentError, "available must be <= 9" unless available <= 9
137
+ raise ArgumentError, "num too large. bytelength must be <= 9" unless num.num_bytes <= 9
138
+ shares = ShamirSecretSharing.split([num.to_s(16)].pack("H*"), available, needed, do_data_checksum=nil)
139
+ shares.map{|i| i.join.to_i }
140
+ end
141
+
142
+ def self.combine(shares)
143
+ shares = shares.map{|i| i.to_s.match(/(\d)(\d)(\d+)/); [$1.to_i, $2.to_i, $3.to_i] }
144
+ ShamirSecretSharing.combine(shares, do_raise=false, do_data_checksum=nil).unpack("H*")[0].to_i(16)
145
+ end
146
+ end
147
+
148
+ class ShareChecksumError < ::StandardError; end
149
+ class ShareDecodeError < ::StandardError; end
150
+ class ShareSanityCheckError < ::StandardError; end
151
+
152
+ class Packed < ShamirSecretSharing # packing format and checkum
153
+ def self.pack(shares)
154
+ shares.map{|x,num_bytes,y|
155
+ buf = [ x, num_bytes, y.to_s(16) ].pack("CnH*")
156
+ checksum = Digest::SHA512.digest(buf)[0...2]
157
+ encode(checksum << buf)
158
+ }
159
+ end
160
+ def self.unpack(shares)
161
+ shares.map{|i|
162
+ buf = decode(i) rescue nil
163
+ raise ShareDecodeError, "share: #{i}" unless buf
164
+ checksum, buf = buf.unpack("a2a*")
165
+ raise ShareChecksumError, "share: #{i}" unless checksum == Digest::SHA512.digest(buf)[0...2]
166
+ i = buf.unpack("CnH*"); [ i[0], i[1], i[2].to_i(16) ]
167
+ }
168
+ end
169
+ end
170
+
171
+ class Base58 < Packed
172
+ def self.encode(string)
173
+ string = string.unpack("H*")[0]
174
+ leading_zero_bytes = (string.match(/^([0]+)/) ? $1 : '').size / 2
175
+ ("1"*leading_zero_bytes) + int_to_base58( string.to_i(16) )
176
+ end
177
+ def self.decode(string)
178
+ leading_zero_bytes = (string.match(/^([1]+)/) ? $1 : '').size
179
+ buf = base58_to_int(string).to_s(16); buf = (buf.bytesize.odd? ? '0'+buf : buf)
180
+ [ ("00"*leading_zero_bytes) + buf ].pack("H*")
181
+ end
182
+ def self.int_to_base58(int_val, leading_zero_bytes=0)
183
+ alpha, base58_val, base = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", "", 58
184
+ while int_val > 0
185
+ int_val, remainder = int_val.divmod(base)
186
+ base58_val = alpha[remainder] + base58_val
187
+ end; base58_val
188
+ end
189
+
190
+ def self.base58_to_int(base58_val)
191
+ alpha, base = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", 58
192
+ base58_val.reverse.each_char.with_index.inject(0) do |int_val, (char,index)|
193
+ raise ArgumentError, 'Value not a valid Base58 String.' unless char_index = alpha.index(char)
194
+ int_val + char_index*(base**index)
195
+ end
196
+ end
197
+ end
198
+
199
+ class Base64 < Packed
200
+ def self.encode(string); [string].pack("m0"); end
201
+ def self.decode(string); string.unpack("m0")[0]; end
202
+ end
203
+
204
+ class Hex < Packed
205
+ def self.encode(string); string.unpack("H*")[0]; end
206
+ def self.decode(string); [string].pack("H*"); end
207
+ end
208
+ end
209
+
210
+
211
+
212
+
213
+ if $0 == __FILE__
214
+ require "minitest/autorun"
215
+
216
+ class MiniTest::Unit::TestCase
217
+ def assert_raises_and_message(klass, msg, &blk)
218
+ err = assert_raises(klass, &blk); assert_equal msg, err.message
219
+ end
220
+ end
221
+
222
+
223
+ class TestShamirSecretSharing < MiniTest::Unit::TestCase
224
+
225
+ def helper(&b)
226
+ [ [6,3], [10, 2], [3,2], [100, 30] ].each{|available,needed| b.call(available, needed) }
227
+ end
228
+
229
+
230
+ def test_shamir_base58
231
+ secret = "hello"
232
+ helper{|available,needed|
233
+ shares = ShamirSecretSharing::Base58.split(secret, available, needed)
234
+ assert_equal secret, ShamirSecretSharing::Base58.combine(shares.shuffle[0...needed])
235
+ }
236
+
237
+ shares = ShamirSecretSharing::Base58.split_with_sanity_check(secret, available=3, needed=2)
238
+ assert_equal secret, ShamirSecretSharing::Base58.combine(shares.shuffle[0...needed])
239
+ end
240
+
241
+ def test_shamir_base64
242
+ secret = "hello"
243
+ helper{|available,needed|
244
+ shares = ShamirSecretSharing::Base64.split(secret, available, needed)
245
+ assert_equal secret, ShamirSecretSharing::Base64.combine(shares.shuffle[0...needed])
246
+ }
247
+ end
248
+
249
+ def test_shamir_hex
250
+ secret = "hello"
251
+ helper{|available,needed|
252
+ shares = ShamirSecretSharing::Hex.split(secret, available, needed)
253
+ assert_equal secret, ShamirSecretSharing::Hex.combine(shares.shuffle[0...needed])
254
+ }
255
+ end
256
+
257
+ def test_shamir_number
258
+ secret = 123
259
+ shares = ShamirSecretSharing::Number.split(secret, 6, 3)
260
+ assert_equal secret, ShamirSecretSharing::Number.combine(shares.shuffle[0...3])
261
+ end
262
+
263
+ def test_shamir_base58_encrypt
264
+ text = "A"*32
265
+ helper{|available,needed|
266
+ shares, encrypted = ShamirSecretSharing::Base58.encrypt(text, available, needed, 96)
267
+ assert_equal text, ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...needed], encrypted)
268
+ }
269
+
270
+ shares, encrypted = ShamirSecretSharing::Base58.encrypt_with_sanity_check(text, available=3, needed=2)
271
+ assert_equal text, ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...needed], encrypted)
272
+ end
273
+
274
+ def test_shamir_base58_encrypt_sanity_checks
275
+ klass = ShamirSecretSharing::Base58
276
+ checks, success = 5, true
277
+ [
278
+ [2,3], [2,4], [3,5]
279
+ ].each{|needed,available|
280
+ checks.times{
281
+ data = "A"*32
282
+ shares, encrypted = klass.encrypt(data, available, needed, 96)
283
+ needed.upto(available).each{|n|
284
+ shares.permutation(n).each{|shares| success = false if klass.decrypt(shares, encrypted, true) != data }
285
+ }
286
+ (needed-1).downto(2).each{|n|
287
+ shares.permutation(n).each{|shares| success = false if klass.decrypt(shares, encrypted, true) != false }
288
+ }
289
+ break unless success
290
+ }
291
+ }
292
+ assert_equal true, success
293
+ end
294
+
295
+ def test_shamir_base64_encrypt
296
+ text = "A"*32
297
+ helper{|available,needed|
298
+ shares, encrypted = ShamirSecretSharing::Base64.encrypt(text, available, needed, 96)
299
+ assert_equal text, ShamirSecretSharing::Base64.decrypt(shares.shuffle[0...needed], encrypted)
300
+ }
301
+ end
302
+
303
+ def test_shamir_hex_encrypt
304
+ text = "A"*32
305
+ helper{|available,needed|
306
+ shares, encrypted = ShamirSecretSharing::Hex.encrypt(text, available, needed, 96)
307
+ assert_equal text, ShamirSecretSharing::Hex.decrypt(shares.shuffle[0...needed], encrypted)
308
+ }
309
+ end
310
+
311
+ def test_shamir_with_broken_share_checksum
312
+ secret = "hello"
313
+ share_with_broken_checksum = ShamirSecretSharing::Base58.encode("foobar")
314
+ share_with_broken_encoding = "1Il"
315
+ shares = ShamirSecretSharing::Base58.split(secret, 3, 2)
316
+ assert_equal false, ShamirSecretSharing::Base58.combine( [shares.shuffle.first, share_with_broken_checksum])
317
+ assert_equal false, ShamirSecretSharing::Base58.combine( [shares.shuffle.first, share_with_broken_encoding])
318
+
319
+ do_raise = true
320
+ err = assert_raises(ShamirSecretSharing::ShareChecksumError){ ShamirSecretSharing::Base58.combine( [shares.shuffle.first, share_with_broken_checksum], do_raise) }
321
+ assert_match /share: /, err.message
322
+ assert_raises(ShamirSecretSharing::ShareDecodeError){ ShamirSecretSharing::Base58.combine( [shares.shuffle.first, share_with_broken_encoding], do_raise) }
323
+ assert_match /share: /, err.message
324
+ end
325
+
326
+ def test_shamir_encrypt_with_broken_encypted_data
327
+ text = "A"*32
328
+ broken_encrypted_data = ShamirSecretSharing::Base58.encode("foobar")
329
+ broken_encrypted_data_encoding = "1Il"
330
+ share_with_broken_encoding = "1Il"
331
+ shares, encrypted = ShamirSecretSharing::Base58.encrypt(text, 3, 2, 96)
332
+ assert_equal false, ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...2], broken_encrypted_data)
333
+ assert_equal false, ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...2], broken_encrypted_data_encoding)
334
+
335
+ do_raise = true
336
+ assert_raises(OpenSSL::Cipher::CipherError) { ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...2], broken_encrypted_data, do_raise) }
337
+ err = assert_raises(ShamirSecretSharing::ShareDecodeError){ ShamirSecretSharing::Base58.decrypt( [shares.shuffle.first, share_with_broken_encoding], encrypted, do_raise) }
338
+ assert_match /share: /, err.message
339
+ err = assert_raises(ShamirSecretSharing::ShareDecodeError){ ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...2], broken_encrypted_data_encoding, do_raise) }
340
+ assert_match /encrypted_data: /, err.message
341
+ end
342
+
343
+ def test_shamir_split_argument_errors
344
+ assert_raises_and_message(ArgumentError, "needed must be <= available") { ShamirSecretSharing::Base58.split("foobar", 2, 3) }
345
+ assert_raises_and_message(ArgumentError, "needed must be >= 2") { ShamirSecretSharing::Base58.split("foobar", 3, 1) }
346
+ assert_raises_and_message(ArgumentError, "available must be <= 250") { ShamirSecretSharing::Base58.split("foobar", 251, 2) }
347
+ assert_raises_and_message(ArgumentError, "bytelength of secret must be >= 1") { ShamirSecretSharing::Base58.split("", 3, 2) }
348
+ assert_raises_and_message(ArgumentError, "bytelength of secret must be <= 4096") { ShamirSecretSharing::Base58.split("A"*4097, 3, 2) }
349
+ end
350
+
351
+ end
352
+
353
+ =begin
354
+ require 'pp'
355
+
356
+ pp shares = ShamirSecretSharing::Base58.split("hello", 6, 3)
357
+ pp ShamirSecretSharing::Base58.combine(shares[0...3])
358
+
359
+ pp shares = ShamirSecretSharing::Base64.split("hello", 6, 3)
360
+ pp ShamirSecretSharing::Base64.combine(shares[0...3])
361
+
362
+ pp shares = ShamirSecretSharing::Hex.split("hello", 6, 3)
363
+ pp ShamirSecretSharing::Hex.combine(shares[0...3])
364
+
365
+ pp shares = ShamirSecretSharing::Number.split(123, 6, 3)
366
+ pp ShamirSecretSharing::Number.combine(shares[0...3])
367
+
368
+
369
+ shares, encrypted = ShamirSecretSharing::Base58.encrypt("A"*32, 6, 3, 96)
370
+ pp [shares, encrypted]
371
+ p ShamirSecretSharing::Base58.decrypt(shares.shuffle[0...3], encrypted)
372
+
373
+ shares, encrypted = ShamirSecretSharing::Base64.encrypt("A"*32, 6, 3, 96)
374
+ pp [shares, encrypted]
375
+ p ShamirSecretSharing::Base64.decrypt(shares.shuffle[0...3], encrypted)
376
+
377
+ shares, encrypted = ShamirSecretSharing::Hex.encrypt("A"*32, 6, 3, 96)
378
+ pp [shares, encrypted]
379
+ p ShamirSecretSharing::Hex.decrypt(shares.shuffle[0...3], encrypted)
380
+ =end
381
+
382
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "shamir-secret-sharing"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "extended-shamir-secret-sharing"
7
+ s.version = ShamirSecretSharing::VERSION
8
+ s.authors = ["lian", "Armand Mégrot"]
9
+ s.email = ["meta.rb@gmail.com", "armand.megrot@cashbee.fr"]
10
+ s.homepage = ""
11
+ s.summary = %q{Gem for Shamir's Secret Sharing}
12
+ s.description = %q{Gem for Shamir's Secret Sharing}
13
+
14
+ s.rubyforge_project = "shamir-secret-sharing"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.required_rubygems_version = ">= 1.3.6"
22
+ end
metadata ADDED
@@ -0,0 +1,48 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: extended-shamir-secret-sharing
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - lian
8
+ - Armand Mégrot
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-05-15 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Gem for Shamir's Secret Sharing
15
+ email:
16
+ - meta.rb@gmail.com
17
+ - armand.megrot@cashbee.fr
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - README.rdoc
23
+ - Rakefile
24
+ - lib/shamir-secret-sharing.rb
25
+ - shamir-secret-sharing.gemspec
26
+ homepage: ''
27
+ licenses: []
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.3.6
43
+ requirements: []
44
+ rubygems_version: 3.0.1
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: Gem for Shamir's Secret Sharing
48
+ test_files: []