secret_sharing 0.0.1

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
+ SHA1:
3
+ metadata.gz: 7d1f9dc3ed57b5e31acda563d2a75bb39f17cda8
4
+ data.tar.gz: 38914666ac426bcdd7765ab4d04b9cd4209e48aa
5
+ SHA512:
6
+ metadata.gz: 05149dab9a3f6afceb1d52a72f15742bd4d5de9a06580d7abb8389d40b96a5de5af908c207e11cd5dc3c66f8c525fcf7c240a3882a1cf2a183327dd87e21b946
7
+ data.tar.gz: 1c6b21a99d780e527b9de6858a9f8bd9a3d3480904d8bc108b99d05dae446583157569be5d15f1da9c507600feb5748b92736aa1064bb2429a25409386003ac8
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ InstalledFiles
7
+ _yardoc
8
+ coverage
9
+ doc/
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
17
+ *.bundle
18
+ *.so
19
+ *.o
20
+ *.a
21
+ mkmf.log
22
+ :w
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ Style/AsciiComments:
2
+ Enabled: false # not to comment in another language than english but to be
3
+ # able to write tests for non ascii characters
4
+
5
+ Documentation:
6
+ Enabled: false # this is handled by yard and it is annoying since rubocop does
7
+ # not notice if a top level class documentation has already
8
+ # been created (https://github.com/bbatsov/rubocop/issues/691)
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.3
4
+ bundler_args: --without development
5
+ cache: bundler
data/.yardops ADDED
@@ -0,0 +1 @@
1
+ lib/**/*.rb --no-private --markup markdown - README.md LICENSE
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in secret_sharing.gemspec
4
+ gem 'bundler', '~> 1.6'
5
+ gem 'rake', group: [:development, :test]
6
+ gem 'rspec', group: [:development, :test]
7
+ gem 'yard', group: :development
8
+ gem 'rubocop', require: false, group: :development
9
+ gem 'simplecov', require: false, group: [:development, :test]
10
+ gem 'coveralls', require: false, group: [:development, :test]
data/Gemfile.lock ADDED
@@ -0,0 +1,68 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ ast (2.0.0)
5
+ astrolabe (1.3.0)
6
+ parser (>= 2.2.0.pre.3, < 3.0)
7
+ coveralls (0.7.1)
8
+ multi_json (~> 1.3)
9
+ rest-client
10
+ simplecov (>= 0.7)
11
+ term-ansicolor
12
+ thor
13
+ diff-lcs (1.2.5)
14
+ docile (1.1.5)
15
+ mime-types (2.4.2)
16
+ multi_json (1.10.1)
17
+ netrc (0.8.0)
18
+ parser (2.2.0.pre.5)
19
+ ast (>= 1.1, < 3.0)
20
+ slop (~> 3.4, >= 3.4.5)
21
+ powerpack (0.0.9)
22
+ rainbow (2.0.0)
23
+ rake (10.3.2)
24
+ rest-client (1.7.2)
25
+ mime-types (>= 1.16, < 3.0)
26
+ netrc (~> 0.7)
27
+ rspec (3.1.0)
28
+ rspec-core (~> 3.1.0)
29
+ rspec-expectations (~> 3.1.0)
30
+ rspec-mocks (~> 3.1.0)
31
+ rspec-core (3.1.7)
32
+ rspec-support (~> 3.1.0)
33
+ rspec-expectations (3.1.2)
34
+ diff-lcs (>= 1.2.0, < 2.0)
35
+ rspec-support (~> 3.1.0)
36
+ rspec-mocks (3.1.3)
37
+ rspec-support (~> 3.1.0)
38
+ rspec-support (3.1.2)
39
+ rubocop (0.26.1)
40
+ astrolabe (~> 1.3)
41
+ parser (>= 2.2.0.pre.4, < 3.0)
42
+ powerpack (~> 0.0.6)
43
+ rainbow (>= 1.99.1, < 3.0)
44
+ ruby-progressbar (~> 1.4)
45
+ ruby-progressbar (1.6.0)
46
+ simplecov (0.9.1)
47
+ docile (~> 1.1.0)
48
+ multi_json (~> 1.0)
49
+ simplecov-html (~> 0.8.0)
50
+ simplecov-html (0.8.0)
51
+ slop (3.6.0)
52
+ term-ansicolor (1.3.0)
53
+ tins (~> 1.0)
54
+ thor (0.19.1)
55
+ tins (1.3.3)
56
+ yard (0.8.7.4)
57
+
58
+ PLATFORMS
59
+ ruby
60
+
61
+ DEPENDENCIES
62
+ bundler (~> 1.6)
63
+ coveralls
64
+ rake
65
+ rspec
66
+ rubocop
67
+ simplecov
68
+ yard
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 flower-pot
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ [![Build Status](https://travis-ci.org/duse-io/secret_sharing_ruby.svg?branch=master)](https://travis-ci.org/duse-io/secret_sharing_ruby)
2
+ [![Coverage Status](https://img.shields.io/coveralls/duse-io/secret_sharing_ruby.svg)](https://coveralls.io/r/duse-io/secret_sharing_ruby)
3
+ [![Code Climate](https://codeclimate.com/github/duse-io/secret_sharing_ruby/badges/gpa.svg)](https://codeclimate.com/github/duse-io/secret_sharing_ruby)
4
+
5
+ # SecretSharing
6
+
7
+ > **Warning:** This implementation has not been tested in production nor has it
8
+ > been examined by a security audit. All uses are your own responsibility.
9
+
10
+ A ruby implementation of shamir's secret sharing.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'secret_sharing'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install secret_sharing
25
+
26
+ ## Usage
27
+
28
+ require 'secret_sharing'
29
+ shares = SecretSharing.split_secret('secret', 2, 3) # => [...]
30
+ length = shares.length # => 3
31
+ secret = SecretSharing.recover_secret(shares[0..1]) # => 'my secret'
32
+
33
+ This implementation can also handle non-ascii characters, however, the charset
34
+ will be visible in the calculated shares. Thus splitting ascii only strings is
35
+ "more" secure. (but always remember, shamir's secret sharing alone is not
36
+ secure)
37
+
38
+ ## Compatible libraries
39
+
40
+ Since this implementation is special in some ways most [Shamir’s Secret
41
+ Sharing](http://de.wikipedia.org/wiki/Shamir%E2%80%99s_Secret_Sharing)
42
+ libraries are not compatible. The only library that is compatible as of now is
43
+ [duse-io/secret-sharing-dart](https://github.com/duse-io/secret-sharing-dart).
44
+
45
+ We do have [integration
46
+ tests](https://github.com/duse-io/lib-integration-tests) that make sure the
47
+ libraries work with each other.
48
+
49
+ ## Contributing
50
+
51
+ 1. Fork it ( https://github.com/duse-io/secret_sharing/fork )
52
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
53
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
54
+ 4. Push to the branch (`git push origin my-new-feature`)
55
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ begin
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
6
+ rescue LoadError
7
+ puts 'rspec tasks could not be loaded'
8
+ end
@@ -0,0 +1,55 @@
1
+ require 'secret_sharing/version'
2
+ require 'secret_sharing/point'
3
+ require 'secret_sharing/polynomial'
4
+ require 'secret_sharing/prime'
5
+ require 'secret_sharing/charset'
6
+ require 'secret_sharing/share'
7
+
8
+ # A ruby implementation of Shamir's Secret Sharing. This module is the only
9
+ # connection to be consumed by a user. Do not use any other class/module of
10
+ # this library unless you really know what you are doing.
11
+ module SecretSharing
12
+ # Split a secret using Shamir's Secret Sharing algorithm.
13
+ #
14
+ # Example
15
+ #
16
+ # SecretSharing.split_secret('secret', 2, 3)
17
+ # # => ["tcesr-1-4e16", "tcesr-2-1105", "tcesr-3-1d3f3"]
18
+ #
19
+ # @param secret_string [String] Secret to split.
20
+ # @param share_threshold [Integer] Number of shares to recover the secret.
21
+ # @param num_shares [Integer] Total number of shares to generate.
22
+ #
23
+ # @return [Array] Array of shares that can be used to recover the secret.
24
+ def split_secret(secret_string, share_threshold, num_shares)
25
+ charset = Charset.by_string secret_string
26
+ secret_int = charset.s_to_i(secret_string)
27
+ points = Polynomial.points_from_secret(secret_int,
28
+ share_threshold,
29
+ num_shares)
30
+ points.map do |point|
31
+ Share.new(charset, point).to_s
32
+ end
33
+ end
34
+
35
+ # Recover a secret with an array of string shares generated with
36
+ # {#split_secret}.
37
+ #
38
+ # Example
39
+ #
40
+ # SecretSharing.recover_secret(["tcesr-1-4e16", "tcesr-2-1105"])
41
+ # # => "secret"
42
+ #
43
+ # @param raw_shares [Array] Shares (array of strings) to recover the secret
44
+ #
45
+ # @return [String] Recovered secret in a string representation.
46
+ def recover_secret(raw_shares)
47
+ shares = raw_shares.map { |raw_share| Share.from_string raw_share }
48
+ points = shares.map(&:point)
49
+ secret_int = Polynomial.modular_lagrange_interpolation(points)
50
+ shares.first.charset.i_to_s(secret_int)
51
+ end
52
+
53
+ module_function :split_secret,
54
+ :recover_secret
55
+ end
@@ -0,0 +1,226 @@
1
+ require 'set'
2
+
3
+ module SecretSharing
4
+ # This module is used to create or retrieve a charset to use.
5
+ module Charset
6
+ # Decides which charset fits the provided string best and creates a custom
7
+ # charset if no predefined charset fits the usecase.
8
+ #
9
+ # Example
10
+ #
11
+ # SecretSharing::Charset.by_string 'test'
12
+ # # => #<SecretSharing::ASCIICharset @charset=[...]>
13
+ #
14
+ # Or with a custom charset
15
+ #
16
+ # SecretSharing::Charset.by_string 'testä'
17
+ # # => #<SecretSharing::DynamicCharset @charset=["s", "e", "ä", "t"]>
18
+ #
19
+ # @param string [String] The string to evaluate the charset for.
20
+ # @return A charset that has at least the methods #s_to_i and #i_to_s.
21
+ def by_string(string)
22
+ return ASCIICharset.new if ASCIICharset.new.subset?(string)
23
+ DynamicCharset.from_string string
24
+ end
25
+
26
+ # Retrieves a charset based on its string representation.
27
+ #
28
+ # Example
29
+ #
30
+ # SecretSharing::Charset.by_charset_string ''
31
+ # # => #<SecretSharing::ASCIICharset @charset=[...]>
32
+ #
33
+ # Or in case of a custom charset
34
+ #
35
+ # SecretSharing::Charset.by_charset_string 'tesä'
36
+ # # => #<SecretSharing::DynamicCharset @charset=["t", "e", "s", "ä"]>
37
+ #
38
+ # @param charset_string [String] The string to evaluate the charset for.
39
+ # @return A charset that has at least the methods #s_to_i and #i_to_s.
40
+ def by_charset_string(charset_string)
41
+ return ASCIICharset.new if charset_string.empty?
42
+ DynamicCharset.new(charset_string.chars)
43
+ end
44
+
45
+ module_function :by_string,
46
+ :by_charset_string
47
+ end
48
+
49
+ module Charset
50
+ # This objects of this class can represent a custom charset whenever the
51
+ # predefined charsets do not fit a situation.
52
+ class DynamicCharset
53
+ # A new instance of DynamicCharset. The constructor should only be used
54
+ # when you know what you are doing. Usually you only want to use this
55
+ # constructor when you recreate a charset and the order of the charset is
56
+ # important. The "null-byte" character is prepended to be the first
57
+ # character in the charset to avoid loosing the first character of the
58
+ # charset when it is also the first character in a string to convert.
59
+ #
60
+ # Example
61
+ #
62
+ # SecretSharing::Charset::DynamicCharset.new ['a', 'b', 'c']
63
+ # # => #<SecretSharing::Charset::DynamicCharset @charset=[...]>
64
+ #
65
+ # @param charset [Array] array of characters to use for the charset.
66
+ def initialize(charset)
67
+ @charset = charset.unshift("\u0000")
68
+ end
69
+
70
+ # Create a charset based on a string to encode.
71
+ #
72
+ # Example
73
+ #
74
+ # SecretSharing::Charset::DynamicCharset.from_string 'test'
75
+ # # => #<SecretSharing::Charset::DynamicCharset @charset=['e','t','s']>
76
+ #
77
+ # @param string [String] a string to encode with the charset to generate
78
+ # @return [DynamicCharset] shuffled charset to encode strings to ints
79
+ def self.from_string(string)
80
+ DynamicCharset.new string.chars.shuffle.uniq
81
+ end
82
+
83
+ # Calculate a string from an integer.
84
+ #
85
+ # Example
86
+ #
87
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
88
+ # charset.i_to_s 6
89
+ # # => "ab"
90
+ #
91
+ # @param x [Integer] integer to convert to string
92
+ # @return [String] converted string
93
+ def i_to_s(x)
94
+ unless x.is_a?(Integer) && x >= 0
95
+ fail ArgumentError, 'x must be a non-negative integer'
96
+ end
97
+
98
+ output = ''
99
+ while x > 0
100
+ x, codepoint = x.divmod(length)
101
+ output.prepend(codepoint_to_char(codepoint))
102
+ end
103
+ output
104
+ end
105
+
106
+ # Calculate an integer from a string.
107
+ #
108
+ # Example
109
+ #
110
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
111
+ # charset.s_to_i "ab"
112
+ # # => 6
113
+ #
114
+ # @param string [Integer] integer to convert to string
115
+ # @return [String] converted string
116
+ def s_to_i(string)
117
+ string.chars.reduce(0) do |output, char|
118
+ output * length + char_to_codepoint(char)
119
+ end
120
+ end
121
+
122
+ # Convert an integer into its string representation according to the
123
+ # charset. (only one character)
124
+ #
125
+ # Example
126
+ #
127
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
128
+ # charset.codepoint_to_char 1
129
+ # # => "a"
130
+ #
131
+ # @param codepoint [Integer] Codepoint to retrieve the character for
132
+ # @return [String] Retrieved character
133
+ def codepoint_to_char(codepoint)
134
+ char = @charset[codepoint]
135
+ return char unless char.nil?
136
+ fail ArgumentError, "Codepoint #{codepoint} does not exist in charset"
137
+ end
138
+
139
+ # Convert a single character into its integer representation according to
140
+ # the charset.
141
+ #
142
+ # Example
143
+ #
144
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
145
+ # charset.char_to_codepoint 'a'
146
+ # # => 1
147
+ #
148
+ # @param c [String] Character to retrieve its codepoint in the charset
149
+ # @return [Integer] Codepoint within the charset
150
+ def char_to_codepoint(c)
151
+ codepoint = @charset.rindex c
152
+ return codepoint unless codepoint.nil?
153
+ error_msg = "Character \"#{c}\" not part of the supported charset"
154
+ fail ArgumentError, error_msg
155
+ end
156
+
157
+ # Total length of the charset
158
+ #
159
+ # Example
160
+ #
161
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
162
+ # charset.length
163
+ # # => 4
164
+ def length
165
+ @charset.length
166
+ end
167
+
168
+ # Adds a string representation of the charset to the string, which can
169
+ # later be used to recreate the charset.
170
+ #
171
+ # Example
172
+ #
173
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
174
+ # charset.add_to_point('1-2')
175
+ # # => "abc-1-2"
176
+ #
177
+ # @param point_string [String] string to prepend to
178
+ # @return [String] string representation of charset-point_string
179
+ def add_to_point(point_string)
180
+ "#{@charset[1...length].join}-#{point_string}"
181
+ end
182
+
183
+ # Check if the provided string can be represented by the charset.
184
+ #
185
+ # Example
186
+ #
187
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
188
+ # charset.subset? 'd'
189
+ # # => false
190
+ # charset.subset? 'a'
191
+ # # => true
192
+ #
193
+ # @param string [String] Character to retrieve the for codepoint
194
+ # @return [TrueClass|FalseClass]
195
+ def subset?(string)
196
+ (Set.new(string.chars.uniq) - Set.new(@charset)).empty?
197
+ end
198
+ end
199
+
200
+ # Charset that can represent any string that only consists of ASCII
201
+ # characters.
202
+ class ASCIICharset < DynamicCharset
203
+ # An instance of an ASCIICharset is just a DynamicCharset with the ASCII
204
+ # characters.
205
+ def initialize
206
+ super((1..127).to_a.map(&:chr))
207
+ end
208
+
209
+ # Adds a string representation of the charset to the string (empty string
210
+ # in case of ASCII, since it is the default charset), which can later be
211
+ # used to recreate the charset.
212
+ #
213
+ # Example
214
+ #
215
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
216
+ # charset.add_to_point('1-2')
217
+ # # => "abc-1-2"
218
+ #
219
+ # @param point_string [String] string to prepend to
220
+ # @return [String] string representation of charset-point_string
221
+ def add_to_point(point_string)
222
+ point_string
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,63 @@
1
+ module SecretSharing
2
+ # This class is used to store a two dimensional point consisting of integers.
3
+ class Point
4
+ attr_accessor :x, :y
5
+
6
+ # Create a Point object.
7
+ #
8
+ # Examples
9
+ #
10
+ # Point.new(1, 2)
11
+ # # => #<Point: @x=1 @y=2>
12
+ #
13
+ # @param x [Integer] The x value of the point
14
+ # @param y [Integer] The y value of the point
15
+ def initialize(x, y)
16
+ @x = x
17
+ @y = y
18
+ end
19
+
20
+ # A nicer inspection of a point object than by default.
21
+ #
22
+ # @return [String] String representation of a Point
23
+ #
24
+ # Examples
25
+ #
26
+ # SecretSharing::Point.new(1, 2).inspect
27
+ # # => "#<Point: @x=1 @y=2>"
28
+ def inspect
29
+ "#<Point: @x=#{x} @y=#{y}>"
30
+ end
31
+
32
+ def to_s
33
+ "#{x}-#{y.to_s(16)}"
34
+ end
35
+
36
+ def self.from_string(point_string)
37
+ x_string, y_string = point_string.split '-'
38
+ Point.new x_string.to_i, y_string.to_i(16)
39
+ end
40
+
41
+ # An implementation similar to Array#transpose for Arrays of Points
42
+ #
43
+ # @return [Array] Two Arrays in an Array
44
+ #
45
+ # Examples
46
+ #
47
+ # point1 = SecretSharing::Point.new(1, 2)
48
+ # point2 = SecretSharing::Point.new(3, 4)
49
+ # points = [point1, point2]
50
+ # Point.transpose(points)
51
+ # # => [[1, 3], [2, 4]]
52
+ def self.transpose(points)
53
+ x_values = []
54
+ y_values = []
55
+
56
+ points.each do |point|
57
+ x_values << point.x
58
+ y_values << point.y
59
+ end
60
+ [x_values, y_values]
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,123 @@
1
+ require 'securerandom'
2
+
3
+ module SecretSharing
4
+ # The polynomial is used to represent the required random polynomials used in
5
+ # Shamir's Secret Sharing algorithm.
6
+ class Polynomial
7
+ # Create a new instance of a Polynomial with n coefficients, when having
8
+ # the polynomial in standard polynomial form.
9
+ #
10
+ # Example
11
+ #
12
+ # Polynomial.new [1, 2, 3]
13
+ # # => #<SecretSharing::Polynomial:0x0000000 @coefficients=[1, 2, 3]>
14
+ #
15
+ # @param coefficients [Array] an array of integers as the coefficients
16
+ def initialize(coefficients)
17
+ @coefficients = coefficients
18
+ end
19
+
20
+ # Generate points on the polynomial, that can be used to reconstruct the
21
+ # polynomial with.
22
+ #
23
+ # Example
24
+ #
25
+ # SecretSharing::Polynomial.new([1, 2, 3, 4]).points(3, 7)
26
+ # # => [#<Point: @x=1 @y=3>, #<Point: @x=2 @y=0>, #<Point: @x=3 @y=2>]
27
+ #
28
+ # @param num_points [Integer] number of points to generate
29
+ # @param prime [Integer] prime for calculation in finite field
30
+ # @return [Array] array of calculated points
31
+ def points(num_points, prime)
32
+ (1..num_points).map do |x|
33
+ y = @coefficients[0]
34
+ (1...@coefficients.length).each do |i|
35
+ exponentiation = x**i % prime
36
+ term = (@coefficients[i] * exponentiation) % prime
37
+ y = (y + term) % prime
38
+ end
39
+ Point.new(x, y)
40
+ end
41
+ end
42
+
43
+ # Generate a random polynomial with a specific degree, defined x=0 value
44
+ # and an upper limit for the coefficients of the polynomial.
45
+ #
46
+ # Example
47
+ #
48
+ # Polynomial.random(2, 3, 7)
49
+ # # => #<SecretSharing::Polynomial:0x0000000 @coefficients=[3, 0, 4]>
50
+ #
51
+ # @param degree [Integer] degree of the polynomial to generate
52
+ # @param intercept [Integer] the y value for x=0
53
+ # @param upper_bound [Integer] the highest value of a single coefficient
54
+ def self.random(degree, intercept, upper_bound)
55
+ fail ArgumentError, 'Degree must be a non-negative number' if degree < 0
56
+
57
+ coefficients = (0...degree).reduce([intercept]) do |accumulator, _i|
58
+ accumulator << SecureRandom.random_number(upper_bound)
59
+ end
60
+ Polynomial.new coefficients
61
+ end
62
+
63
+ # Generate points from a secret integer.
64
+ #
65
+ # Example
66
+ #
67
+ # SecretSharing::Polynomial.points_from_secret(123, 2, 3)
68
+ # # => [#<Point: @x=1 @y=109>, #<Point: @x=2 @y=95>, #<Point: @x=3 @y=81>]
69
+ #
70
+ # @param secret_int [Integer] the secret to divide into points
71
+ # @param point_threshold [Integer] number of points to reconstruct
72
+ # @param num_points [Integer] number of points to generate
73
+ # @return [Polynomial] the generated polynomial
74
+ def self.points_from_secret(secret_int, point_threshold, num_points)
75
+ prime = SecretSharing::Prime.large_enough_prime([secret_int, num_points])
76
+ fail ArgumentError, 'Secret is too long' if prime.nil?
77
+ fail ArgumentError, 'Threshold must be at least 2' if point_threshold < 2
78
+ fail ArgumentError, 'Threshold must be less than the total number of points' if point_threshold > num_points
79
+
80
+ polynomial = SecretSharing::Polynomial.random(point_threshold - 1,
81
+ secret_int,
82
+ prime)
83
+ polynomial.points(num_points, prime)
84
+ end
85
+
86
+ # Modular lagrange interpolation
87
+ def self.modular_lagrange_interpolation(points)
88
+ y_values = Point.transpose(points)[1]
89
+ prime = SecretSharing::Prime.large_enough_prime(y_values)
90
+ points.reduce(0) do |f_x, point|
91
+ numerator, denominator = lagrange_fraction(points, point, prime)
92
+ lagrange_polynomial = numerator * mod_inverse(denominator, prime)
93
+ (prime + f_x + (point.y * lagrange_polynomial)) % prime
94
+ end
95
+ end
96
+
97
+ # part of the lagrange interpolation
98
+ def self.lagrange_fraction(points, current, prime)
99
+ numerator, denominator = 1, 1
100
+ points.each do |point|
101
+ if point != current
102
+ numerator = (numerator * (0 - point.x)) % prime
103
+ denominator = (denominator * (current.x - point.x)) % prime
104
+ end
105
+ end
106
+ [numerator, denominator]
107
+ end
108
+
109
+ # inverse modulo
110
+ def self.mod_inverse(k, prime)
111
+ k = k % prime
112
+ r = egcd(prime, k.abs)[2]
113
+ (prime + r) % prime
114
+ end
115
+
116
+ # extended Euclidean algorithm
117
+ def self.egcd(a, b)
118
+ return [b, 0, 1] if a == 0
119
+ g, y, x = egcd(b % a, a)
120
+ [g, x - b.div(a) * y, y]
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,44 @@
1
+ module SecretSharing
2
+ # This module is used to generate/calculate/retrieve primes.
3
+ module Prime
4
+ # Retrieves the next largest prime for the largest number in batch
5
+ #
6
+ # Example
7
+ #
8
+ # Prime.large_enough_prime [1, 2, 3, 4]
9
+ # # => 7
10
+ #
11
+ # @param batch [Array] a list of integers
12
+ # @return [Integer] the next largest prime or nil if numbers too large
13
+ def large_enough_prime(batch)
14
+ standard_primes = mersenne_primes + [
15
+ # smallest 257, 321 and 385 bit primes
16
+ 2**256 + 297, 2**320 + 27, 2**384 + 231
17
+ ].sort
18
+
19
+ standard_primes.each do |prime|
20
+ greater_than_prime = Array.new(batch).select { |i| i if i > prime }
21
+ return prime if greater_than_prime.empty?
22
+ end
23
+ nil
24
+ end
25
+
26
+ private
27
+
28
+ # Calculates the first 15 mersenne primes
29
+ def mersenne_primes
30
+ mersenne_prime_exponents = [
31
+ 2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279
32
+ ]
33
+ mersenne_prime_exponents.map do |exp|
34
+ prime = 1
35
+ prime *= 2**exp
36
+ prime -= 1
37
+ prime
38
+ end
39
+ end
40
+
41
+ module_function :mersenne_primes,
42
+ :large_enough_prime
43
+ end
44
+ end
@@ -0,0 +1,74 @@
1
+ module SecretSharing
2
+ # A share is an object that encapsulates the properties of a share created by
3
+ # Shamir's Secret Sharing algorithm.
4
+ class Share
5
+ attr_reader :charset, :point
6
+
7
+ # Create a share object
8
+ #
9
+ # Example
10
+ #
11
+ # charset = SecretSharing::ASCIICharset.new
12
+ # point = SecretSharing::Point.new 1, 2
13
+ # SecretSharing::Share.new charset, point
14
+ # # => #<SecretSharing::Share:0x0000000 @charset=..., @point=...>
15
+ #
16
+ # @param charset [SecretSharing::Charset::DynamicCharset]
17
+ # @param point [SecretSharing::Point]
18
+ def initialize(charset, point)
19
+ @charset = charset
20
+ @point = point
21
+ end
22
+
23
+ # A string representation of a share.
24
+ #
25
+ # Example
26
+ #
27
+ # charset = SecretSharing::ASCIICharset.new
28
+ # point = SecretSharing::Point.new 1, 2
29
+ # SecretSharing::Share.new(charset, point).to_s
30
+ # # => "1-2"
31
+ #
32
+ def to_s
33
+ charset.add_to_point(point.to_s)
34
+ end
35
+
36
+ # Creates a share object from its string representation.
37
+ #
38
+ # Example
39
+ #
40
+ # SecretSharing::Share.from_string "1-2"
41
+ # # => #<SecretSharing::Share:0x0000000 @charset=..., @point=...>
42
+ #
43
+ def self.from_string(share_string)
44
+ charset_string, point_string = parse share_string
45
+ charset = Charset.by_charset_string charset_string
46
+ point = Point.from_string(point_string)
47
+ Share.new(charset, point)
48
+ end
49
+
50
+ # Parses the string representation of a share into charset and point
51
+ # coordinates out of a string representation of a share. Since the charset
52
+ # part can have a "-" in it it must be parsed backwards and not just
53
+ # splitted by "-".
54
+ #
55
+ # Example
56
+ #
57
+ # SecretSharing::Share.parse '1-2'
58
+ # # => ['', '1-2']
59
+ #
60
+ # @param share_string [String] a string representation of a share
61
+ # @return an array in form of [charset_string, point_string]
62
+ #
63
+ def self.parse(share_string)
64
+ charset_string, point_string = '', ''
65
+ number_of_dashes = 0
66
+ share_string.chars.reverse.each do |char|
67
+ charset_string.prepend(char) if number_of_dashes >= 2
68
+ number_of_dashes += 1 if char == '-'
69
+ point_string.prepend(char) if number_of_dashes <= 1
70
+ end
71
+ [charset_string, point_string]
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,4 @@
1
+ module SecretSharing
2
+ # gem version
3
+ VERSION = '0.0.1'
4
+ end
@@ -0,0 +1,19 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'secret_sharing/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'secret_sharing'
8
+ spec.version = SecretSharing::VERSION
9
+ spec.authors = ['flower-pot']
10
+ spec.email = ['fbranczyk@gmail.com']
11
+ spec.summary = 'Ruby implementation of sharmir\'s secret sharing'
12
+ spec.description = 'Divide, share and reconstruct secrets.'
13
+ spec.homepage = 'https://github.com/duse-io/secret_sharing_ruby'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.test_files = `git ls-files spec`.split($INPUT_RECORD_SEPARATOR)
18
+ spec.require_paths = ['lib']
19
+ end
@@ -0,0 +1,36 @@
1
+ require 'securerandom'
2
+
3
+ describe SecretSharing::Charset do
4
+ context 'encode and decode' do
5
+ it 'should be able to decode randomly generated encoded strings' do
6
+ 50.times do
7
+ str = SecureRandom.base64(100)
8
+ charset = SecretSharing::Charset.by_charset_string(str)
9
+ encoded = charset.s_to_i(str)
10
+ decoded = charset.i_to_s(encoded)
11
+ expect(decoded).to eq(str)
12
+ end
13
+ end
14
+
15
+ it 'should throw errors when characters that are not in charset' do
16
+ encoder = SecretSharing::Charset.by_charset_string('test')
17
+ expect { encoder.codepoint_to_char(5) }.to raise_error(ArgumentError)
18
+ expect { encoder.char_to_codepoint('r') }.to raise_error(ArgumentError)
19
+ end
20
+
21
+ it 'should throw an error when trying to convert negative numbers' do
22
+ encoder = SecretSharing::Charset.by_charset_string('test')
23
+ expect { encoder.i_to_s(-1) }.to raise_error(ArgumentError)
24
+ end
25
+
26
+ it 'should throw an error when trying to convert something non-integer' do
27
+ encoder = SecretSharing::Charset.by_charset_string('test')
28
+ expect { encoder.i_to_s('error') }.to raise_error(ArgumentError)
29
+ end
30
+
31
+ it 'should return the correct charset for ""' do
32
+ charset = SecretSharing::Charset.by_charset_string('')
33
+ expect(charset).to be_a SecretSharing::Charset::ASCIICharset
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ describe SecretSharing::Point do
2
+ it 'should follow the ruby convention when inspecting' do
3
+ point = SecretSharing::Point.new(1, 2)
4
+ expect(point.inspect).to eq('#<Point: @x=1 @y=2>')
5
+ end
6
+
7
+ it 'should correctly stringify a point' do
8
+ expect(SecretSharing::Point.new(1, 2).to_s).to eq('1-2')
9
+ end
10
+
11
+ it 'should correctly create a point from a correctly formatted string' do
12
+ expect(SecretSharing::Point.new(1, 2).to_s).to eq('1-2')
13
+ end
14
+
15
+ it 'should be able to recreate a point from its string representation' do
16
+ point = SecretSharing::Point.new(1, 2)
17
+ string = point.to_s
18
+ reconstructed_point = SecretSharing::Point.from_string string
19
+ expect(reconstructed_point.x).to eq(point.x)
20
+ expect(reconstructed_point.y).to eq(point.y)
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ describe SecretSharing::Charset do
2
+ it 'should error when trying to generate a polynomial with negative degree' do
3
+ random_generator = -> { SecretSharing::Polynomial.random(-1, 1, 12) }
4
+ expect(&random_generator).to raise_error(ArgumentError)
5
+ end
6
+
7
+ it 'should error when the secret is to long' do
8
+ secret = 2**10_000
9
+ block = -> { SecretSharing::Polynomial.points_from_secret(secret, 2, 3) }
10
+ expect(&block).to raise_error(ArgumentError)
11
+ end
12
+
13
+ it 'should error when the point threshold is too small' do
14
+ block = -> { SecretSharing::Polynomial.points_from_secret(123, 1, 3) }
15
+ expect(&block).to raise_error(ArgumentError)
16
+ end
17
+
18
+ it 'should error when the point threshold is larger than total points' do
19
+ block = -> { SecretSharing::Polynomial.points_from_secret(123, 3, 2) }
20
+ expect(&block).to raise_error(ArgumentError)
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ describe SecretSharing::Prime do
2
+ context '#get_large_enough_prime' do
3
+ it 'should calc the correct prime' do
4
+ expect(SecretSharing::Prime.large_enough_prime([1, 2, 3, 4])).to eq(7)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ require 'securerandom'
2
+
3
+ describe SecretSharing do
4
+ it 'should encrypt and decrypt correctly' do
5
+ 50.times do
6
+ secret = SecureRandom.base64(130)
7
+ num_of_shares = 2 + SecureRandom.random_number(100)
8
+ shares = SecretSharing.split_secret(secret, 2, num_of_shares)
9
+ expect(SecretSharing.recover_secret(shares[0..1])).to eq(secret)
10
+ end
11
+ end
12
+
13
+ it 'should work with non ascii characters as well' do
14
+ secret = 'κόσμε'
15
+ num_of_shares = 2 + SecureRandom.random_number(100)
16
+ shares = SecretSharing.split_secret(secret, 2, num_of_shares)
17
+ expect(SecretSharing.recover_secret(shares[0..1])).to eq(secret)
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ describe SecretSharing::Share do
2
+ it 'correctly parses a string representation of a share' do
3
+ expect(SecretSharing::Share.parse('$$ASCII-1-2')).to eq(['$$ASCII', '1-2'])
4
+ end
5
+
6
+ it 'correctly parses a share when charset contains a "-"' do
7
+ expect(SecretSharing::Share.parse('tes--1-2')).to eq(['tes-', '1-2'])
8
+ end
9
+ end
@@ -0,0 +1,55 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider
11
+ # making a separate helper file that requires the additional dependencies and
12
+ # performs the additional setup, and require it from the spec files that
13
+ # actually need it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+
20
+ unless ENV['CI']
21
+ require 'simplecov'
22
+ SimpleCov.start
23
+ end
24
+
25
+ if ENV['CI']
26
+ require 'coveralls'
27
+ Coveralls.wear!
28
+ end
29
+
30
+ require 'secret_sharing'
31
+
32
+ RSpec.configure do |config|
33
+ # rspec-expectations config goes here. You can use an alternate
34
+ # assertion/expectation library such as wrong or the stdlib/minitest
35
+ # assertions if you prefer.
36
+ config.expect_with :rspec do |expectations|
37
+ # This option will default to `true` in RSpec 4. It makes the `description`
38
+ # and `failure_message` of custom matchers include text for helper methods
39
+ # defined using `chain`, e.g.:
40
+ # be_bigger_than(2).and_smaller_than(4).description
41
+ # # => "be bigger than 2 and smaller than 4"
42
+ # ...rather than:
43
+ # # => "be bigger than 2"
44
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
45
+ end
46
+
47
+ # rspec-mocks config goes here. You can use an alternate test double
48
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
49
+ config.mock_with :rspec do |mocks|
50
+ # Prevents you from mocking or stubbing a method that does not exist on
51
+ # a real object. This is generally recommended, and will default to
52
+ # `true` in RSpec 4.
53
+ mocks.verify_partial_doubles = true
54
+ end
55
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: secret_sharing
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - flower-pot
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Divide, share and reconstruct secrets.
14
+ email:
15
+ - fbranczyk@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - ".rubocop.yml"
23
+ - ".travis.yml"
24
+ - ".yardops"
25
+ - Gemfile
26
+ - Gemfile.lock
27
+ - LICENSE
28
+ - README.md
29
+ - Rakefile
30
+ - lib/secret_sharing.rb
31
+ - lib/secret_sharing/charset.rb
32
+ - lib/secret_sharing/point.rb
33
+ - lib/secret_sharing/polynomial.rb
34
+ - lib/secret_sharing/prime.rb
35
+ - lib/secret_sharing/share.rb
36
+ - lib/secret_sharing/version.rb
37
+ - secret_sharing.gemspec
38
+ - spec/charset_spec.rb
39
+ - spec/point_spec.rb
40
+ - spec/polynomial_spec.rb
41
+ - spec/prime_spec.rb
42
+ - spec/secret_sharing_spec.rb
43
+ - spec/share_spec.rb
44
+ - spec/spec_helper.rb
45
+ homepage: https://github.com/duse-io/secret_sharing_ruby
46
+ licenses:
47
+ - MIT
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 2.4.3
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Ruby implementation of sharmir's secret sharing
69
+ test_files:
70
+ - spec/charset_spec.rb
71
+ - spec/point_spec.rb
72
+ - spec/polynomial_spec.rb
73
+ - spec/prime_spec.rb
74
+ - spec/secret_sharing_spec.rb
75
+ - spec/share_spec.rb
76
+ - spec/spec_helper.rb
77
+ has_rdoc: