secretsharing 0.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,93 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Copyright 2011-2015 Alexander Klink and Glenn Rempe
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module SecretSharing
18
+ module Shamir
19
+ # The SecretSharing::Shamir::Container class can be used to share random
20
+ # secrets between n people, so that k < n people can recover the
21
+ # secret, but k-1 people learn nothing (in an information-theoretical
22
+ # sense) about the secret.
23
+ #
24
+ # For a theoretical background, see:
25
+ # http://www.cs.tau.ac.il/~bchor/Shamir.html
26
+ # http://en.wikipedia.org/wiki/Secret_sharing#Shamir.27s_scheme
27
+ #
28
+ class Container
29
+ include SecretSharing::Shamir
30
+ attr_reader :n, :k, :secret, :shares
31
+
32
+ MIN_SHARES = 2
33
+ MAX_SHARES = 512
34
+
35
+ # To create a new SecretSharing::Shamir::Container object, you can
36
+ # pass either just n, or n and k where:
37
+ #
38
+ # n = The total number of shares that will be created.
39
+ # k = The threshold number of the total shares needed to
40
+ # recreate the original secret. (Default = n)
41
+ #
42
+ # For example:
43
+ #
44
+ # # 3(k) out of 5(n) shares needed to recover secret
45
+ # s = SecretSharing::Shamir::Container.new(5, 3)
46
+ #
47
+ # # 3(k) out of 3(n) shares needed to recover secret
48
+ # s = SecretSharing::Shamir::Container.new(3)
49
+ #
50
+ def initialize(n, k = n)
51
+ @n = n.to_i
52
+ @k = k.to_i
53
+
54
+ fail ArgumentError, 'n must be an Integer' unless @n.is_a?(Integer)
55
+ fail ArgumentError, 'k must be an Integer' unless @k.is_a?(Integer)
56
+
57
+ fail ArgumentError, 'k must be <= n' unless @k <= @n
58
+ fail ArgumentError, 'k must be >= #{MIN_SHARES}' unless @k >= MIN_SHARES
59
+ fail ArgumentError, 'n must be <= #{MAX_SHARES}' unless @n <= MAX_SHARES
60
+
61
+ @secret = nil
62
+ @shares = []
63
+ end
64
+
65
+ def secret?
66
+ @secret.is_a?(SecretSharing::Shamir::Secret)
67
+ end
68
+
69
+ def secret=(sec)
70
+ fail ArgumentError, 'secret has already been set' if secret?
71
+ fail ArgumentError, 'secret must be a SecretSharing::Shamir::Secret instance' unless sec.is_a?(SecretSharing::Shamir::Secret)
72
+ @secret = sec
73
+ @shares = Share.create_shares(@k, @n, @secret)
74
+ true
75
+ end
76
+
77
+ # Add a secret share to the object. Accepts a
78
+ # SecretSharing::Shamir::Share instance.
79
+ # Returns secret as a SecretSharing::Shamir::Secret if enough valid shares have been added
80
+ # to recover the secret, and false otherwise. The secret can also be recovered
81
+ # later with SecretSharing::Shamir::Container#secret if enough valid shares were previously
82
+ # provided.
83
+ def <<(share)
84
+ # You can't add more shares than were originally generated with value of @n
85
+ fail ArgumentError, 'You have added more shares than allowed by the value of @n' if @shares.size >= @n
86
+
87
+ share = SecretSharing::Shamir::Share.new(:share => share) unless share.is_a?(SecretSharing::Shamir::Share)
88
+ @shares << share unless @shares.include?(share)
89
+ @secret = Share.recover_secret(@shares)
90
+ end
91
+ end # class Container
92
+ end # module Shamir
93
+ end # module SecretSharing
@@ -0,0 +1,123 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Copyright 2011-2015 Glenn Rempe
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module SecretSharing
18
+ module Shamir
19
+ # A SecretSharing::Shamir::Secret object represents a Secret in the
20
+ # Shamir secret sharing scheme. Secrets can be passed in as an input
21
+ # argument when creating a new SecretSharing::Shamir::Container or
22
+ # can be the output from a Container that has successfully decoded shares.
23
+ # A new Secret take 0 or 1 args. Zero args means the Secret will be initialized
24
+ # with a random OpenSSL::BN object with the Secret::DEFAULT_BITLENGTH. If a
25
+ # single argument is passed it can be one of two object types, String or
26
+ # OpenSSL::BN. If a String it is expected to be a specially encoded String
27
+ # that was generated as the output of calling #to_s on another Secret object.
28
+ # If the object type is OpenSSL::BN it can represent a number up to 4096 num_bits
29
+ # in length as reported by OpenSSL::BN#num_bits.
30
+ #
31
+ # All secrets are internally represented as an OpenSSL::BN which can be retrieved
32
+ # in its raw form using #secret.
33
+ #
34
+ class Secret
35
+ include SecretSharing::Shamir
36
+
37
+ # FIXME : Is a MAX_BITLENGTH really needed? Can it be larger if so?
38
+
39
+ MAX_BITLENGTH = 4096
40
+
41
+ attr_accessor :secret, :bitlength, :hmac
42
+
43
+ # FIXME : allow instantiating a secret with any random number bitlength you choose.
44
+
45
+ def initialize(opts = {})
46
+ opts = {
47
+ :secret => get_random_number(256)
48
+ }.merge!(opts)
49
+
50
+ # override with options
51
+ opts.each_key do |k|
52
+ if self.respond_to?("#{k}=")
53
+ send("#{k}=", opts[k])
54
+ else
55
+ fail ArgumentError, "Argument '#{k}' is not allowed"
56
+ end
57
+ end
58
+
59
+ # FIXME : Do we really need the ability for a String arg to re-instantiate a Secret?
60
+ # FIXME : If its a String, shouldn't it be able to be an arbitrary String converted to/from OpenSSL::BN?
61
+
62
+ if opts[:secret].is_a?(String)
63
+ # Decode a Base64.urlsafe_encode64 String which contains a Base 36 encoded Bignum back into an OpenSSL::BN
64
+ # See : Secret#to_s for forward encoding method.
65
+ decoded_secret = usafe_decode64(opts[:secret])
66
+ fail ArgumentError, 'invalid base64 (returned nil or empty String)' if decoded_secret.empty?
67
+ @secret = OpenSSL::BN.new(decoded_secret.to_i(36).to_s)
68
+ end
69
+
70
+ @secret = opts[:secret] if @secret.nil?
71
+ fail ArgumentError, "Secret must be an OpenSSL::BN, not a '#{@secret.class}'" unless @secret.is_a?(OpenSSL::BN)
72
+ @bitlength = @secret.num_bits
73
+ fail ArgumentError, "Secret must have a bitlength less than or equal to #{MAX_BITLENGTH}" if @bitlength > MAX_BITLENGTH
74
+
75
+ generate_hmac
76
+ end
77
+
78
+ # Secrets are equal if the OpenSSL::BN in @secret is the same.
79
+ def ==(other)
80
+ other == @secret
81
+ end
82
+
83
+ # Set a new secret forces regeneration of the HMAC
84
+ def secret=(secret)
85
+ @secret = secret
86
+ generate_hmac
87
+ end
88
+
89
+ def secret?
90
+ @secret.is_a?(OpenSSL::BN)
91
+ end
92
+
93
+ def to_s
94
+ # Convert the OpenSSL::BN secret to an Bignum which has a #to_s(36) method
95
+ # Convert the Bignum to a Base 36 encoded String
96
+ # Wrap the Base 36 encoded String as a URL safe Base 64 encoded String
97
+ # Combined this should result in a relatively compact and portable String
98
+ usafe_encode64(@secret.to_i.to_s(36))
99
+ end
100
+
101
+ def valid_hmac?
102
+ return false if !@secret.is_a?(OpenSSL::BN) || @hmac.to_s.empty? || @secret.to_s.empty?
103
+
104
+ hmac_key = @secret.to_s
105
+ hmac_data = OpenSSL::Digest::SHA256.new(@secret.to_s).hexdigest
106
+
107
+ @hmac == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, hmac_key, hmac_data)
108
+ end
109
+
110
+ private
111
+
112
+ # The HMAC uses the raw secret itself as the HMAC key, and the SHA256 of the secret as the data.
113
+ # This allows later regeneration of the HMAC to confirm that the restored secret is in fact
114
+ # identical to what was originally split into shares.
115
+ def generate_hmac
116
+ return false if @secret.to_s.empty?
117
+ hmac_key = @secret.to_s
118
+ hmac_data = OpenSSL::Digest::SHA256.new(@secret.to_s).hexdigest
119
+ @hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, hmac_key, hmac_data)
120
+ end
121
+ end # class Secret
122
+ end # module Shamir
123
+ end # module SecretSharing
@@ -0,0 +1,156 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Copyright 2011-2015 Alexander Klink and Glenn Rempe
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module SecretSharing
18
+ module Shamir
19
+ # A SecretSharing::Shamir::Share object represents a share in the
20
+ # Shamir secret sharing scheme. The share consists of a point (x,y) on
21
+ # a polynomial over Z/Zp, where p is a prime.
22
+ class Share
23
+ include SecretSharing::Shamir
24
+ extend SecretSharing::Shamir
25
+ attr_accessor :share, :version, :hmac, :k, :n, :x, :y, :prime, :prime_bitlength
26
+
27
+ def initialize(opts = {})
28
+ opts = {
29
+ :share => nil,
30
+ :version => 1,
31
+ :hmac => nil,
32
+ :k => nil,
33
+ :n => nil,
34
+ :x => nil,
35
+ :y => nil,
36
+ :prime => nil,
37
+ :prime_bitlength => nil
38
+ }.merge!(opts)
39
+
40
+ opts.each_key do |k|
41
+ if self.respond_to?("#{k}=")
42
+ send("#{k}=", opts[k])
43
+ else
44
+ fail ArgumentError, "Argument '#{k}' is not allowed"
45
+ end
46
+ end
47
+
48
+ # Decode and unpack a String share if provided
49
+ unpack_share(@share) unless @share.nil?
50
+
51
+ if @share.nil?
52
+ errors = [:version, :hmac, :k, :n, :x, :y, :prime, :prime_bitlength].map { |e| e if send("#{e}").nil? }.compact
53
+ fail ArgumentError, "#{errors.join(', ')} expected." unless errors.empty?
54
+ end
55
+ end
56
+
57
+ def ==(other)
58
+ other.to_s == to_s
59
+ end
60
+
61
+ # FIXME : Add an HMAC which 'signs' all of the attributes of the hash and which gets verified on re-hydration to make sure
62
+ # that none of the attributes changed?
63
+
64
+ def to_hash
65
+ [:version, :hmac, :k, :n, :x, :y, :prime, :prime_bitlength].reduce({}) do |h, element|
66
+ if [:hmac].include?(element)
67
+ h.merge(element => send(element))
68
+ else
69
+ # everything else is an Integer/Bignum
70
+ h.merge(element => send(element).to_i)
71
+ end
72
+ end
73
+ end
74
+
75
+ def to_json
76
+ MultiJson.dump(to_hash)
77
+ end
78
+
79
+ def to_s
80
+ usafe_encode64(to_json)
81
+ end
82
+
83
+ # Creates the shares by computing random coefficients for a polynomial
84
+ # and then computing points on this polynomial.
85
+ def self.create_shares(k, n, secret)
86
+ shares = []
87
+ coefficients = []
88
+ coefficients[0] = secret.secret
89
+
90
+ # round up to next nibble
91
+ next_nibble_bitlength = secret.bitlength + (4 - (secret.bitlength % 4))
92
+ prime_bitlength = next_nibble_bitlength + 1
93
+ prime = OpenSSL::BN.generate_prime(prime_bitlength)
94
+
95
+ # FIXME : Why does generate_prime always return 35879 for bitlength 1-15
96
+ # OpenSSL::BN::generate_prime(1).to_i
97
+ # => 35879
98
+ # Do we need to make sure that prime_bitlength is not shorter than 64 bits?
99
+ # See : https://www.mail-archive.com/openssl-dev@openssl.org/msg18835.html
100
+ # See : http://ardoino.com/2005/11/maths-openssl-primes-random/
101
+ # See : http://www.openssl.org/docs/apps/genrsa.html "Therefore the number of bits should not be less that 64."
102
+
103
+ # compute random coefficients
104
+ (1..k - 1).each { |x| coefficients[x] = get_random_number(secret.bitlength) }
105
+
106
+ (1..n).each do |x|
107
+ p_x = evaluate_polynomial_at(x, coefficients, prime)
108
+ new_share = new(:x => x, :y => p_x, :prime => prime, :prime_bitlength => prime_bitlength, :k => k, :n => n, :hmac => secret.hmac)
109
+ shares[x - 1] = new_share
110
+ end
111
+ shares
112
+ end
113
+
114
+ # Recover the secret by doing Lagrange interpolation.
115
+ def self.recover_secret(shares)
116
+ return false unless shares.length >= shares[0].k
117
+
118
+ # All Shares must have the same HMAC or they were derived from different Secrets
119
+ hmacs = shares.map(&:hmac).uniq
120
+ fail ArgumentError, 'Share mismatch. Not all Shares have a common HMAC.' unless hmacs.size == 1
121
+
122
+ secret = SecretSharing::Shamir::Secret.new(:secret => OpenSSL::BN.new('0'))
123
+
124
+ shares.each do |share|
125
+ l_x = lagrange(share.x, shares)
126
+ summand = share.y * l_x
127
+ summand %= share.prime
128
+ secret.secret += summand
129
+ secret.secret %= share.prime
130
+ end
131
+
132
+ if secret && secret.is_a?(SecretSharing::Shamir::Secret) && secret.valid_hmac?
133
+ return secret
134
+ else
135
+ fail ArgumentError, 'Secret recovery failure. The generated Secret does not match the HMACs in the Shares provided.'
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ def unpack_share(share)
142
+ decoded = usafe_decode64(share)
143
+ h = MultiJson.load(decoded, :symbolize_keys => true)
144
+
145
+ @version = h[:version].to_i unless h[:version].nil?
146
+ @hmac = h[:hmac] unless h[:hmac].nil?
147
+ @k = h[:k].to_i unless h[:k].nil?
148
+ @n = h[:n].to_i unless h[:n].nil?
149
+ @x = h[:x].to_i unless h[:x].nil?
150
+ @y = OpenSSL::BN.new(h[:y].to_s) unless h[:y].nil?
151
+ @prime = OpenSSL::BN.new(h[:prime].to_s) unless h[:prime].nil?
152
+ @prime_bitlength = h[:prime_bitlength].to_i unless h[:prime_bitlength].nil?
153
+ end
154
+ end # class Share
155
+ end # module Shamir
156
+ end # module SecretSharing
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Copyright 2011-2015 Glenn Rempe
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ # Module for specifying the current version of the gem.
18
+ module SecretSharing
19
+ VERSION = '1.0.0'
20
+ end
@@ -0,0 +1,47 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'secretsharing/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "secretsharing"
9
+ s.version = SecretSharing::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ["Alexander Klink", "Glenn Rempe"]
12
+ s.email = ["glenn@rempe.us"]
13
+ s.homepage = "https://github.com/grempe/secretsharing"
14
+ s.summary = %q{A Ruby Gem to enable sharing secrets using Shamirs Secret Sharing.}
15
+ s.license = "APACHE 2.0"
16
+
17
+ s.has_rdoc = 'true'
18
+ s.extra_rdoc_files = ['README.md']
19
+
20
+ s.rubyforge_project = "secretsharing"
21
+
22
+ s.files = `git ls-files`.split($/)
23
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
25
+ s.require_paths = ["lib"]
26
+
27
+ s.add_dependency 'highline', '~> 1.6'
28
+ s.add_dependency 'multi_json', '~> 1.10'
29
+
30
+ s.add_development_dependency 'mocha'
31
+ s.add_development_dependency 'minitest'
32
+ s.add_development_dependency 'coco'
33
+ s.add_development_dependency 'rb-fsevent'
34
+ s.add_development_dependency 'rerun'
35
+ s.add_development_dependency 'rubocop'
36
+ s.add_development_dependency 'bundler', '~> 1.7'
37
+ s.add_development_dependency 'rake', '~> 10.4'
38
+
39
+ s.description =<<'XEOF'
40
+ Shamir's Secret Sharing is an algorithm in cryptography. It is a
41
+ form of secret sharing, where a secret is divided into parts,
42
+ giving each participant its own unique part, where some of the
43
+ parts or all of them are needed in order to reconstruct the
44
+ secret. Holders of a share gain no knowledge of the larger secret.
45
+ XEOF
46
+
47
+ end
@@ -0,0 +1,373 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Copyright 2011-2015 Glenn Rempe
4
+
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require File.expand_path('../spec_helper', __FILE__)
18
+
19
+ describe SecretSharing::Shamir::Container do
20
+
21
+ describe 'initialization' do
22
+
23
+ it 'will raise when instantiated with no args' do
24
+ lambda { SecretSharing::Shamir::Container.new }.must_raise(ArgumentError)
25
+ end
26
+
27
+ it 'will create shares with n and k equal when given one Integer arg' do
28
+ s1 = SecretSharing::Shamir::Container.new(5)
29
+ s1.n.must_equal(5)
30
+ s1.k.must_equal(5)
31
+ end
32
+
33
+ it 'will create shares with n and k set to their own values when given two Integer args' do
34
+ s1 = SecretSharing::Shamir::Container.new(5, 3)
35
+ s1.n.must_equal(5)
36
+ s1.k.must_equal(3)
37
+ end
38
+
39
+ it 'will create shares with n and k equal when given one Integer as String arg' do
40
+ s1 = SecretSharing::Shamir::Container.new('5')
41
+ s1.n.must_equal(5)
42
+ s1.k.must_equal(5)
43
+ end
44
+
45
+ it 'will raise an exception with n being a non-Integer String arg' do
46
+ lambda { SecretSharing::Shamir::Container.new('foo') }.must_raise(ArgumentError)
47
+ end
48
+
49
+ it 'will create shares with n and k set to their own values when given two Integer as String args' do
50
+ s1 = SecretSharing::Shamir::Container.new('5', '3')
51
+ s1.n.must_equal(5)
52
+ s1.k.must_equal(3)
53
+ end
54
+
55
+ it 'will return false when secret? is called after initialization with only n arg set' do
56
+ s1 = SecretSharing::Shamir::Container.new(5)
57
+ s1.secret?.must_equal(false)
58
+ end
59
+
60
+ it 'will return false when secret? is called after initialization with n and k arg set' do
61
+ s1 = SecretSharing::Shamir::Container.new(5, 3)
62
+ s1.secret?.must_equal(false)
63
+ end
64
+
65
+ it 'will return nil secret when called after initialization with only n arg set' do
66
+ s1 = SecretSharing::Shamir::Container.new(5)
67
+ s1.secret.must_be_nil
68
+ end
69
+
70
+ it 'will return nil secret when called after initialization with n and k arg set' do
71
+ s1 = SecretSharing::Shamir::Container.new(5, 3)
72
+ s1.secret.must_be_nil
73
+ end
74
+
75
+ it 'will raise if k > n' do
76
+ lambda { SecretSharing::Shamir::Container.new(5, 6) }.must_raise(ArgumentError)
77
+ end
78
+
79
+ it 'will raise if only n is provided and it is < 2' do
80
+ lambda { SecretSharing::Shamir::Container.new(1) }.must_raise(ArgumentError)
81
+ end
82
+
83
+ it 'will raise unless k >= 2' do
84
+ lambda { SecretSharing::Shamir::Container.new(1, 1) }.must_raise(ArgumentError)
85
+ end
86
+
87
+ it 'will initialize if both k and n are at max size of 512' do
88
+ s1 = SecretSharing::Shamir::Container.new(512, 512)
89
+ s1.n.must_equal(512)
90
+ s1.k.must_equal(512)
91
+ end
92
+
93
+ it 'will raise if n > 512' do
94
+ lambda { SecretSharing::Shamir::Container.new(513) }.must_raise(ArgumentError)
95
+ end
96
+
97
+ it 'must return the correct min shares constant value' do
98
+ SecretSharing::Shamir::Container::MIN_SHARES.must_equal(2)
99
+ end
100
+
101
+ it 'must return the correct max shares constant value' do
102
+ SecretSharing::Shamir::Container::MAX_SHARES.must_equal(512)
103
+ end
104
+
105
+ end # describe initialization
106
+
107
+ describe 'creating a container and setting a secret' do
108
+
109
+ before do
110
+ @num_shares = 5
111
+ @c = SecretSharing::Shamir::Container.new(@num_shares)
112
+ @secret_num = OpenSSL::BN.new('1234567890')
113
+ @c.secret = SecretSharing::Shamir::Secret.new(:secret => @secret_num)
114
+ end
115
+
116
+ it 'will return true from #secret?' do
117
+ @c.secret?.must_equal(true)
118
+ end
119
+
120
+ it 'will not return a nil #secret' do
121
+ @c.secret.wont_be_nil
122
+ end
123
+
124
+ it 'will not return a nil #shares' do
125
+ @c.shares.wont_be_nil
126
+ end
127
+
128
+ it 'will return an Array of #shares' do
129
+ @c.shares.must_be_instance_of(Array)
130
+ end
131
+
132
+ it 'will return an Array of #shares of the same length as initialized with' do
133
+ @c.shares.size.must_equal(@num_shares)
134
+ end
135
+
136
+ it 'will return an Array of #shares each of the correct class' do
137
+ @c.shares.each do |share|
138
+ share.must_be_instance_of(SecretSharing::Shamir::Share)
139
+ end
140
+ end
141
+
142
+ it 'must raise an exception if a secret is attempted to be set more than once' do
143
+ lambda { @c.secret = SecretSharing::Shamir::Secret.new }.must_raise(ArgumentError)
144
+ end
145
+
146
+ end # creating a container and setting a secret
147
+
148
+ describe 'generating shares when a secret is provided' do
149
+
150
+ it 'should generate unique shares for the min number of shares and a tiny secret' do
151
+ c1 = SecretSharing::Shamir::Container.new(2)
152
+ c1.secret = SecretSharing::Shamir::Secret.new(:secret => OpenSSL::BN.new('123'))
153
+ shares = c1.shares
154
+ shares.size.must_equal(2)
155
+ uniq_shares = shares.uniq
156
+ shares.must_equal(uniq_shares)
157
+ end
158
+
159
+ it 'should generate unique shares for the max number of shares and a tiny secret' do
160
+ c1 = SecretSharing::Shamir::Container.new(512)
161
+ c1.secret = SecretSharing::Shamir::Secret.new(:secret => OpenSSL::BN.new('123'))
162
+ shares = c1.shares
163
+ shares.size.must_equal(512)
164
+ uniq_shares = shares.uniq
165
+ shares.must_equal(uniq_shares)
166
+ end
167
+
168
+ it 'should generate unique shares for the min number of shares and a large secret' do
169
+ c1 = SecretSharing::Shamir::Container.new(2)
170
+ # FIXME : Major Perf Issue : If OpenSSL::BN::rand(512) is given with 4096 instead of 512 this takes FOREVER
171
+ c1.secret = SecretSharing::Shamir::Secret.new(:secret => OpenSSL::BN::rand(512))
172
+ shares = c1.shares
173
+ shares.size.must_equal(2)
174
+ uniq_shares = shares.uniq
175
+ shares.must_equal(uniq_shares)
176
+ end
177
+
178
+ it 'should generate unique shares for the max number of shares and a large secret' do
179
+ c1 = SecretSharing::Shamir::Container.new(512)
180
+ # FIXME : Major Perf Issue : If OpenSSL::BN::rand(512) is given with 4096 instead of 512 this takes FOREVER
181
+ c1.secret = SecretSharing::Shamir::Secret.new(:secret => OpenSSL::BN::rand(512))
182
+ shares = c1.shares
183
+ shares.size.must_equal(512)
184
+ uniq_shares = shares.uniq
185
+ shares.must_equal(uniq_shares)
186
+ end
187
+
188
+ end
189
+
190
+ describe 'recovering a secret from a container' do
191
+
192
+ before do
193
+ @c1 = SecretSharing::Shamir::Container.new(5) # creator
194
+ @c2 = SecretSharing::Shamir::Container.new(5) # recipient
195
+
196
+ @c3 = SecretSharing::Shamir::Container.new(5, 3) # creator
197
+ @c4 = SecretSharing::Shamir::Container.new(5, 3) # recipient
198
+
199
+ @bad = SecretSharing::Shamir::Container.new(5, 3) # a bad actor
200
+ end
201
+
202
+ describe 'with a mix of valid and invalid shares' do
203
+
204
+ before do
205
+ # set a secret on the 'creators'
206
+ @c1.secret = SecretSharing::Shamir::Secret.new
207
+ @c3.secret = SecretSharing::Shamir::Secret.new
208
+ @bad.secret = SecretSharing::Shamir::Secret.new
209
+ end
210
+
211
+ it 'should be able to recover secret when k equals n and all k valid shares are provided as Shamir::Share objects' do
212
+ @c2 << @c1.shares[0]
213
+ @c2 << @c1.shares[1]
214
+ @c2 << @c1.shares[2]
215
+ @c2 << @c1.shares[3]
216
+
217
+ # with the last remaining share missing
218
+ @c2.secret?.must_equal(false)
219
+
220
+ @c2 << @c1.shares[4]
221
+
222
+ # with the final share provided
223
+ @c2.secret?.must_equal(true)
224
+ @c2.secret.must_equal(@c1.secret)
225
+ end
226
+
227
+ it 'should raise an ArgumentError if n + 1 shares are provided (more than were originally generated)' do
228
+ @c2 << @c1.shares[0]
229
+ @c2 << @c1.shares[1]
230
+ @c2 << @c1.shares[2]
231
+ @c2 << @c1.shares[3]
232
+ @c2.secret?.must_equal(false)
233
+ @c2 << @c1.shares[4]
234
+ @c2.secret?.must_equal(true)
235
+
236
+ lambda { @c2 << @bad.shares[0] }.must_raise(ArgumentError)
237
+ end
238
+
239
+ it 'should raise an ArgumentError when k equals n and k-1 valid shares and 1 invalid share are provided as Shamir::Share objects' do
240
+ @c2 << @c1.shares[0]
241
+ @c2 << @c1.shares[1]
242
+ @c2 << @c1.shares[2]
243
+ @c2 << @c1.shares[3]
244
+
245
+ # with the last remaining share missing
246
+ @c2.secret?.must_equal(false)
247
+
248
+ # with a bad share
249
+ lambda { @c2 << @bad.shares[0] }.must_raise(ArgumentError)
250
+ end
251
+
252
+ it 'should raise an ArgumentError if the final @secret generated does not have a valid_hmac?' do
253
+ # mocha instance mock using handy 'any_instance'
254
+ SecretSharing::Shamir::Secret.any_instance.stubs(:valid_hmac?).returns(false)
255
+ @c2 << @c1.shares[0]
256
+ @c2 << @c1.shares[1]
257
+ @c2 << @c1.shares[2]
258
+ @c2 << @c1.shares[3]
259
+ lambda { @c2 << @c1.shares[4] }.must_raise(ArgumentError)
260
+ end
261
+
262
+ end
263
+
264
+ describe 'with valid shares resulting from a random secret' do
265
+
266
+ before do
267
+ extend SecretSharing::Shamir
268
+ # set a secret on both of the 'creators'
269
+ @c1.secret = SecretSharing::Shamir::Secret.new
270
+ @c3.secret = SecretSharing::Shamir::Secret.new
271
+ end
272
+
273
+ # This exposed a bug in the prime number generation; for instance 4*8
274
+ # passes.
275
+ it 'should be able to recover secret with a bitlength of 5*8' do
276
+ secret = SecretSharing::Shamir::Secret.new(:secret => get_random_number(5*8))
277
+ c = SecretSharing::Shamir::Container.new(4,2)
278
+ c.secret = secret
279
+
280
+ (0...4).to_a.permutation(2).collect{|e| e.sort}.uniq.each do |indexes|
281
+ c2 = SecretSharing::Shamir::Container.new(2)
282
+ indexes.each do |index|
283
+ c2 << c.shares[index]
284
+ end
285
+ c2.secret?.must_equal(true)
286
+ c2.secret.must_equal(c.secret)
287
+ end
288
+ end
289
+
290
+ it 'should be able to recover secret when k equals n and all k shares are provided as Shamir::Share objects' do
291
+ @c2 << @c1.shares[0]
292
+ @c2 << @c1.shares[1]
293
+ @c2 << @c1.shares[2]
294
+ @c2 << @c1.shares[3]
295
+
296
+ # with the last remaining share missing
297
+ @c2.secret?.must_equal(false)
298
+
299
+ @c2 << @c1.shares[4]
300
+
301
+ # with the final share provided
302
+ @c2.secret?.must_equal(true)
303
+ @c2.secret.must_equal(@c1.secret)
304
+ end
305
+
306
+ it 'should be able to recover secret when k equals n and all k shares are provided as Shamir::Share objects converted to Strings' do
307
+ @c2 << @c1.shares[0].to_s
308
+ @c2 << @c1.shares[1].to_s
309
+ @c2 << @c1.shares[2].to_s
310
+ @c2 << @c1.shares[3].to_s
311
+
312
+ # with the last remaining share missing
313
+ @c2.secret?.must_equal(false)
314
+
315
+ @c2 << @c1.shares[4].to_s
316
+
317
+ # with the final share provided
318
+ @c2.secret?.must_equal(true)
319
+ @c2.secret.must_equal(@c1.secret)
320
+ end
321
+
322
+ it 'should be able to recover secret when k < n and minimum k shares are provided as Shamir::Share objects' do
323
+ @c4 << @c3.shares[0]
324
+ @c4 << @c3.shares[1]
325
+
326
+ # with the last remaining share missing
327
+ @c4.secret?.must_equal(false)
328
+
329
+ @c4 << @c3.shares[2]
330
+
331
+ # with the final share provided
332
+ @c4.secret?.must_equal(true)
333
+ @c4.secret.must_equal(@c3.secret)
334
+ end
335
+
336
+ it 'should be able to recover secret when k < n and minimum k shares are provided as Shamir::Share objects converted to Strings' do
337
+ @c4 << @c3.shares[0].to_s
338
+ @c4 << @c3.shares[1].to_s
339
+
340
+ # with the last remaining share missing
341
+ @c4.secret?.must_equal(false)
342
+
343
+ @c4 << @c3.shares[2].to_s
344
+
345
+ # with the final share provided
346
+ @c4.secret?.must_equal(true)
347
+ @c4.secret.must_equal(@c3.secret)
348
+ end
349
+
350
+ it 'should be able to recover secret when k < n and more than minimum k shares are provided as Shamir::Share objects converted to Strings' do
351
+ @c4 << @c3.shares[0].to_s
352
+ @c4 << @c3.shares[1].to_s
353
+
354
+ # with the last remaining share missing
355
+ @c4.secret?.must_equal(false)
356
+
357
+ @c4 << @c3.shares[2].to_s
358
+
359
+ # with the final needed share provided
360
+ @c4.secret?.must_equal(true)
361
+ @c4.secret.must_equal(@c3.secret)
362
+
363
+ # with an extra valid share provided
364
+ @c4 << @c3.shares[3].to_s
365
+ @c4.secret?.must_equal(true)
366
+ @c4.secret.must_equal(@c3.secret)
367
+ end
368
+
369
+ end
370
+
371
+ end # recovering a secret from a container
372
+
373
+ end # describe SecretSharing::Shamir::Container