secret_sharing 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9edad808096f5d69277724baf3e5bda37c3508a4
4
- data.tar.gz: c53c0ef4912e5c122e7bf762c8786fd06f096aa9
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MTU5NTYxM2RmYzRmOTdlMzVlYzczNTg1ODc2NDBmYTcwOGNiNWY2Yg==
5
+ data.tar.gz: !binary |-
6
+ NjA3MGFiOGNiYzlhOGQ0MWIxYjNkM2VlNDVkMGZiYmQ2MzgyZTgzZA==
5
7
  SHA512:
6
- metadata.gz: d82ab5514fc4c4279e98b4020665d261fcbe51f931fcdd8e95df2d26851022ece62c2a907361f5f145d41c96fa6127c1b198621a6f687ea295c41da4e692fadd
7
- data.tar.gz: 02b27fd7e5512185118e086c8b967e2bd1f3741dc51c2bccd7c6dfe6d73fb9025f690e19f01aa3b1abb0ef7fc0c9f27b3fa558b0130ac9b76bb10071306cf122
8
+ metadata.gz: !binary |-
9
+ NGM2YjkzZDQyMGYyNGU5NzhhMGUxZjAxMTVmZTg4MGJjNTRkNTI1NGJmYzkw
10
+ NzAwN2Y4NGQ3MmJkZWNlN2NiNjhkM2FkYTExZDFlNTNjMDM4MDYyNDdjNWNi
11
+ MmYyOGUwNTU1ZDE0MTAzZmU4NzUzZGViMmM2OTRiYTUxN2M4NTQ=
12
+ data.tar.gz: !binary |-
13
+ NzRmYjIwMzk2MWJmYTUxY2YxMjIzYzUyOWMwMDVhZGIwZGI5NTIxOTlhNDdl
14
+ OWNjYzczNzJkMmJhOTBjYzk2Yjc3ZWVmMmFmNjI4ZmI3ZWZhM2M0Y2RiNjIz
15
+ MDBkYTU0NGE3NGU3MTg1MmQyYmE4ZmE5MmZmMzI1MjQzNjI3ZWQ=
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --color
2
2
  --require spec_helper
3
+ --format documentation
@@ -1,7 +1,3 @@
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
1
  Documentation:
6
2
  Enabled: false # this is handled by yard and it is annoying since rubocop does
7
3
  # not notice if a top level class documentation has already
@@ -1,6 +1,14 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2.0
4
- bundler_args: --without development
3
+ - 2.2.2
4
+ bundler_args: "--without development"
5
5
  cache: bundler
6
6
  sudo: false
7
+ deploy:
8
+ provider: rubygems
9
+ api_key:
10
+ secure: aNkBOgCW7ZCsDHp/tSv1C2yxu1FVh9XoClyGNc6IXrvdJ/OouvuLucNXxazD53YnbaRqM5QxyJbl2wMNHrlp7aakjSmmPcHcYGWpZU0AzCaIvzCdoLPxL5GMR0K1CIdyL7KDTDv5ntmEA4iINACYcm20v0EBhY02Tbt1DI/nc9c=
11
+ gem: secret_sharing
12
+ on:
13
+ tags: true
14
+ repo: duse-io/secret_sharing_ruby
data/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ [![Gem Version](https://badge.fury.io/rb/secret_sharing.svg)](http://badge.fury.io/rb/secret_sharing)
1
2
  [![Build Status](https://travis-ci.org/duse-io/secret_sharing_ruby.svg?branch=master)](https://travis-ci.org/duse-io/secret_sharing_ruby)
2
3
  [![Coverage Status](https://coveralls.io/repos/duse-io/secret_sharing_ruby/badge.svg?branch=master)](https://coveralls.io/r/duse-io/secret_sharing_ruby?branch=master)
3
4
  [![Code Climate](https://codeclimate.com/github/duse-io/secret_sharing_ruby/badges/gpa.svg)](https://codeclimate.com/github/duse-io/secret_sharing_ruby)
data/Rakefile CHANGED
@@ -1,4 +1,7 @@
1
1
  require 'bundler/gem_tasks'
2
+ # remove release task
3
+ Rake::Task["release"].clear
4
+
2
5
  begin
3
6
  require 'rspec/core/rake_task'
4
7
  RSpec::Core::RakeTask.new(:spec)
@@ -9,6 +9,8 @@ require 'secret_sharing/share'
9
9
  # connection to be consumed by a user. Do not use any other class/module of
10
10
  # this library unless you really know what you are doing.
11
11
  module SecretSharing
12
+ extend self
13
+
12
14
  # Split a secret using Shamir's Secret Sharing algorithm.
13
15
  #
14
16
  # Example
@@ -22,13 +24,13 @@ module SecretSharing
22
24
  #
23
25
  # @return [Array] Array of shares that can be used to recover the secret.
24
26
  def split_secret(secret_string, share_threshold, num_shares)
25
- secret_int = Charset::ASCIICharset.new.s_to_i(secret_string)
27
+ secret_int = Charset::ASCIICharset.s_to_i(secret_string)
26
28
  points = Polynomial.points_from_secret(secret_int,
27
29
  share_threshold,
28
30
  num_shares)
29
31
 
30
32
  points.map do |point|
31
- Share.new(point).to_s
33
+ Share.new(point).to_s(Math.log10(points.length).floor + 1)
32
34
  end
33
35
  end
34
36
 
@@ -47,9 +49,6 @@ module SecretSharing
47
49
  shares = raw_shares.map { |raw_share| Share.from_string raw_share }
48
50
  points = shares.map(&:point)
49
51
  secret_int = Polynomial.modular_lagrange_interpolation(points)
50
- Charset::ASCIICharset.new.i_to_s(secret_int)
52
+ Charset::ASCIICharset.i_to_s(secret_int)
51
53
  end
52
-
53
- module_function :split_secret,
54
- :recover_secret
55
54
  end
@@ -1,138 +1,123 @@
1
1
  require 'set'
2
2
 
3
3
  module SecretSharing
4
- module Charset
5
- # This objects of this class can represent a custom charset whenever the
6
- # predefined charsets do not fit a situation.
7
- class DynamicCharset
8
- # A new instance of DynamicCharset. The constructor should only be used
9
- # when you know what you are doing. Usually you only want to use this
10
- # constructor when you recreate a charset and the order of the charset is
11
- # important. The "null-byte" character is prepended to be the first
12
- # character in the charset to avoid loosing the first character of the
13
- # charset when it is also the first character in a string to convert.
14
- #
15
- # Example
16
- #
17
- # SecretSharing::Charset::DynamicCharset.new ['a', 'b', 'c']
18
- # # => #<SecretSharing::Charset::DynamicCharset @charset=[...]>
19
- #
20
- # @param charset [Array] array of characters to use for the charset.
21
- def initialize(charset)
22
- @charset = charset.unshift("\u0000")
23
- end
4
+ # Charset can represent any charset which does not have the null byte
5
+ class Charset
6
+ class NotInCharset < ArgumentError; end
7
+ class NotPositiveInteger < ArgumentError; end
24
8
 
25
- # Calculate a string from an integer.
26
- #
27
- # Example
28
- #
29
- # charset = SecretSharing::Charset.by_charset_string 'abc'
30
- # charset.i_to_s 6
31
- # # => "ab"
32
- #
33
- # @param x [Integer] integer to convert to string
34
- # @return [String] converted string
35
- def i_to_s(x)
36
- unless x.is_a?(Integer) && x >= 0
37
- fail ArgumentError, 'x must be a non-negative integer'
38
- end
9
+ # @return [Array] internal representation of the charset
10
+ attr_reader :charset
39
11
 
40
- output = ''
41
- while x > 0
42
- x, codepoint = x.divmod(length)
43
- output.prepend(codepoint_to_char(codepoint))
44
- end
45
- output
46
- end
12
+ # The "null-byte" character is prepended to be the first character in the
13
+ # charset to avoid loosing the first character of the charset when it is
14
+ # also the first character in a string to convert.
15
+ #
16
+ # Example
17
+ #
18
+ # SecretSharing::Charset::DynamicCharset.new ['a', 'b', 'c'] # =>
19
+ # #<SecretSharing::Charset::DynamicCharset @charset=[...]>
20
+ #
21
+ # @param charset [Array] array of characters to use for the charset.
22
+ def initialize(charset)
23
+ @charset = ["\u0000"] + charset
24
+ @charset.freeze
25
+ end
47
26
 
48
- # Calculate an integer from a string.
49
- #
50
- # Example
51
- #
52
- # charset = SecretSharing::Charset.by_charset_string 'abc'
53
- # charset.s_to_i "ab"
54
- # # => 6
55
- #
56
- # @param string [Integer] integer to convert to string
57
- # @return [String] converted string
58
- def s_to_i(string)
59
- string.chars.reduce(0) do |output, char|
60
- output * length + char_to_codepoint(char)
61
- end
27
+ # Calculate a string from an integer.
28
+ #
29
+ # Example
30
+ #
31
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
32
+ # charset.i_to_s 6
33
+ # # => "ab"
34
+ #
35
+ # @param input [Integer] integer to convert to string
36
+ # @return [String] converted string
37
+ def i_to_s(input)
38
+ if !input.is_a?(Integer) || input < 0
39
+ fail NotPositiveInteger, 'input must be a non-negative integer'
62
40
  end
63
41
 
64
- # Convert an integer into its string representation according to the
65
- # charset. (only one character)
66
- #
67
- # Example
68
- #
69
- # charset = SecretSharing::Charset.by_charset_string 'abc'
70
- # charset.codepoint_to_char 1
71
- # # => "a"
72
- #
73
- # @param codepoint [Integer] Codepoint to retrieve the character for
74
- # @return [String] Retrieved character
75
- def codepoint_to_char(codepoint)
76
- char = @charset[codepoint]
77
- return char unless char.nil?
78
- fail ArgumentError, "Codepoint #{codepoint} does not exist in charset"
42
+ output = ''
43
+ while input > 0
44
+ input, codepoint = input.divmod(charset.length)
45
+ output.prepend(codepoint_to_char(codepoint))
79
46
  end
47
+ output
48
+ end
80
49
 
81
- # Convert a single character into its integer representation according to
82
- # the charset.
83
- #
84
- # Example
85
- #
86
- # charset = SecretSharing::Charset.by_charset_string 'abc'
87
- # charset.char_to_codepoint 'a'
88
- # # => 1
89
- #
90
- # @param c [String] Character to retrieve its codepoint in the charset
91
- # @return [Integer] Codepoint within the charset
92
- def char_to_codepoint(c)
93
- codepoint = @charset.rindex c
94
- return codepoint unless codepoint.nil?
95
- error_msg = "Character \"#{c}\" not part of the supported charset"
96
- fail ArgumentError, error_msg
50
+ # Calculate an integer from a string.
51
+ #
52
+ # Example
53
+ #
54
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
55
+ # charset.s_to_i "ab"
56
+ # # => 6
57
+ #
58
+ # @param string [Integer] integer to convert to string
59
+ # @return [String] converted string
60
+ def s_to_i(string)
61
+ string.chars.reduce(0) do |output, char|
62
+ output * charset.length + char_to_codepoint(char)
97
63
  end
64
+ end
98
65
 
99
- # Total length of the charset
100
- #
101
- # Example
102
- #
103
- # charset = SecretSharing::Charset.by_charset_string 'abc'
104
- # charset.length
105
- # # => 4
106
- def length
107
- @charset.length
66
+ # Convert an integer into its string representation according to the
67
+ # charset. (only one character)
68
+ #
69
+ # Example
70
+ #
71
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
72
+ # charset.codepoint_to_char 1
73
+ # # => "a"
74
+ #
75
+ # @param codepoint [Integer] Codepoint to retrieve the character for
76
+ # @return [String] Retrieved character
77
+ def codepoint_to_char(codepoint)
78
+ if charset.at(codepoint).nil?
79
+ fail NotInCharset, "Codepoint #{codepoint} does not exist in charset"
108
80
  end
81
+ charset.at(codepoint)
82
+ end
109
83
 
110
- # Check if the provided string can be represented by the charset.
111
- #
112
- # Example
113
- #
114
- # charset = SecretSharing::Charset.by_charset_string 'abc'
115
- # charset.subset? 'd'
116
- # # => false
117
- # charset.subset? 'a'
118
- # # => true
119
- #
120
- # @param string [String] Character to retrieve the for codepoint
121
- # @return [TrueClass|FalseClass]
122
- def subset?(string)
123
- (Set.new(string.chars.uniq) - Set.new(@charset)).empty?
84
+ # Convert a single character into its integer representation according to
85
+ # the charset.
86
+ #
87
+ # Example
88
+ #
89
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
90
+ # charset.char_to_codepoint 'a'
91
+ # # => 1
92
+ #
93
+ # @param c [String] Character to retrieve its codepoint in the charset
94
+ # @return [Integer] Codepoint within the charset
95
+ def char_to_codepoint(c)
96
+ codepoint = charset.index c
97
+ if codepoint.nil?
98
+ fail NotInCharset, "Char \"#{c}\" not part of the supported charset"
124
99
  end
100
+ codepoint
101
+ end
102
+
103
+ # Check if the provided string can be represented by the charset.
104
+ #
105
+ # Example
106
+ #
107
+ # charset = SecretSharing::Charset.by_charset_string 'abc'
108
+ # charset.subset? 'd'
109
+ # # => false
110
+ # charset.subset? 'a'
111
+ # # => true
112
+ #
113
+ # @param string [String] Character to retrieve the for codepoint
114
+ # @return [TrueClass|FalseClass]
115
+ def subset?(string)
116
+ (string.chars - charset).empty?
125
117
  end
126
118
 
127
119
  # Charset that can represent any string that only consists of ASCII
128
120
  # characters.
129
- class ASCIICharset < DynamicCharset
130
- # An instance of an ASCIICharset is just a DynamicCharset with the ASCII
131
- # characters.
132
- def initialize
133
- super((1..127).to_a.map(&:chr))
134
- end
135
- end
121
+ ASCIICharset = new((1..127).to_a.map(&:chr))
136
122
  end
137
123
  end
138
-
@@ -1,7 +1,10 @@
1
1
  module SecretSharing
2
2
  # This class is used to store a two dimensional point consisting of integers.
3
3
  class Point
4
- attr_accessor :x, :y
4
+ # @return [Integer] x value the point was instantiated with
5
+ attr_reader :x
6
+ # @return [Integer] y value the point was instantiated with
7
+ attr_reader :y
5
8
 
6
9
  # Create a Point object.
7
10
  #
@@ -17,18 +20,6 @@ module SecretSharing
17
20
  @y = y
18
21
  end
19
22
 
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
23
  # An implementation similar to Array#transpose for Arrays of Points
33
24
  #
34
25
  # @return [Array] Two Arrays in an Array
@@ -4,17 +4,27 @@ module SecretSharing
4
4
  # The polynomial is used to represent the required random polynomials used in
5
5
  # Shamir's Secret Sharing algorithm.
6
6
  class Polynomial
7
+ attr_reader :coefficients
8
+
7
9
  # Create a new instance of a Polynomial with n coefficients, when having
8
10
  # the polynomial in standard polynomial form.
9
11
  #
10
12
  # Example
11
13
  #
14
+ # For the polynomial f(x) = a0 + a1 * x + a2 * x^2 + ... + an * x^n the
15
+ # coefficients are [a0, a1, a2, ..., an]
16
+ #
12
17
  # Polynomial.new [1, 2, 3]
13
18
  # # => #<SecretSharing::Polynomial:0x0000000 @coefficients=[1, 2, 3]>
14
19
  #
15
20
  # @param coefficients [Array] an array of integers as the coefficients
16
21
  def initialize(coefficients)
22
+ coefficients.each do |c|
23
+ not_an_int = 'One or more coefficents are not integers'
24
+ fail ArgumentError, not_an_int unless c.is_a?(Integer)
25
+ end
17
26
  @coefficients = coefficients
27
+ @coefficients.freeze
18
28
  end
19
29
 
20
30
  # Generate points on the polynomial, that can be used to reconstruct the
@@ -29,8 +39,9 @@ module SecretSharing
29
39
  # @param prime [Integer] prime for calculation in finite field
30
40
  # @return [Array] array of calculated points
31
41
  def points(num_points, prime)
42
+ intercept = @coefficients[0] # the first coefficient is the intercept
32
43
  (1..num_points).map do |x|
33
- y = @coefficients[0]
44
+ y = intercept
34
45
  (1...@coefficients.length).each do |i|
35
46
  exponentiation = x**i % prime
36
47
  term = (@coefficients[i] * exponentiation) % prime
@@ -54,10 +65,10 @@ module SecretSharing
54
65
  def self.random(degree, intercept, upper_bound)
55
66
  fail ArgumentError, 'Degree must be a non-negative number' if degree < 0
56
67
 
57
- coefficients = (0...degree).reduce([intercept]) do |accumulator, _i|
68
+ coefficients = (0...degree).reduce([intercept]) do |accumulator|
58
69
  accumulator << SecureRandom.random_number(upper_bound)
59
70
  end
60
- Polynomial.new coefficients
71
+ new coefficients
61
72
  end
62
73
 
63
74
  # Generate points from a secret integer.
@@ -72,8 +83,7 @@ module SecretSharing
72
83
  # @param num_points [Integer] number of points to generate
73
84
  # @return [Polynomial] the generated polynomial
74
85
  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?
86
+ prime = SecretSharing::Prime.large_enough_prime([secret_int, num_points].max)
77
87
  fail ArgumentError, 'Threshold must be at least 2' if point_threshold < 2
78
88
  fail ArgumentError, 'Threshold must be less than the total number of points' if point_threshold > num_points
79
89
 
@@ -81,12 +91,14 @@ module SecretSharing
81
91
  secret_int,
82
92
  prime)
83
93
  polynomial.points(num_points, prime)
94
+ rescue Prime::CannotFindLargeEnoughPrime
95
+ raise ArgumentError, 'Secret is too long'
84
96
  end
85
97
 
86
98
  # Modular lagrange interpolation
87
99
  def self.modular_lagrange_interpolation(points)
88
100
  y_values = Point.transpose(points)[1]
89
- prime = SecretSharing::Prime.large_enough_prime(y_values)
101
+ prime = SecretSharing::Prime.large_enough_prime(y_values.max)
90
102
  points.reduce(0) do |f_x, point|
91
103
  numerator, denominator = lagrange_fraction(points, point, prime)
92
104
  lagrange_polynomial = numerator * mod_inverse(denominator, prime)
@@ -1,44 +1,46 @@
1
1
  module SecretSharing
2
2
  # This module is used to generate/calculate/retrieve primes.
3
3
  module Prime
4
+ class CannotFindLargeEnoughPrime < StandardError; end
5
+
6
+ extend self
7
+
4
8
  # Retrieves the next largest prime for the largest number in batch
5
9
  #
6
10
  # Example
7
11
  #
8
- # Prime.large_enough_prime [1, 2, 3, 4]
12
+ # Prime.large_enough_prime 4
9
13
  # # => 7
10
14
  #
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
-
15
+ # @param input [Integer] the integer to find the next largest prime for
16
+ # @return [Integer] the next largest prime
17
+ # @raise [CannotFindLargeEnoughPrime] raised when input is too large and
18
+ # no large enough prime can be found
19
+ def large_enough_prime(input)
19
20
  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?
21
+ return prime if prime > input
22
22
  end
23
- nil
23
+ fail CannotFindLargeEnoughPrime, 'Input too large'
24
24
  end
25
25
 
26
26
  private
27
27
 
28
+ # Merges the mersenne primes with the smallst 257, 321, and 385 bit primes
29
+ def standard_primes
30
+ mersenne_primes + [
31
+ # smallest 257, 321 and 385 bit primes
32
+ 2**256 + 297, 2**320 + 27, 2**384 + 231
33
+ ].sort
34
+ end
35
+
28
36
  # Calculates the first 15 mersenne primes
29
37
  def mersenne_primes
30
38
  mersenne_prime_exponents = [
31
39
  2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279
32
40
  ]
33
41
  mersenne_prime_exponents.map do |exp|
34
- prime = 1
35
- prime *= 2**exp
36
- prime -= 1
37
- prime
42
+ (2**exp) - 1
38
43
  end
39
44
  end
40
-
41
- module_function :mersenne_primes,
42
- :large_enough_prime
43
45
  end
44
46
  end
@@ -2,6 +2,7 @@ module SecretSharing
2
2
  # A share is an object that encapsulates the properties of a share created by
3
3
  # Shamir's Secret Sharing algorithm.
4
4
  class Share
5
+ # @return [SecretSharing::Point] Point the share was instantiated with
5
6
  attr_reader :point
6
7
 
7
8
  # Create a share object
@@ -25,8 +26,8 @@ module SecretSharing
25
26
  # SecretSharing::Share.new(point).to_s
26
27
  # # => "1-2"
27
28
  #
28
- def to_s
29
- "#{point.x}-#{point.y.to_s(16)}"
29
+ def to_s(x_length)
30
+ "#{point.x.to_s.rjust(x_length, '0')}-#{point.y.to_s(16)}"
30
31
  end
31
32
 
32
33
  # Creates a share object from its string representation.
@@ -36,10 +37,12 @@ module SecretSharing
36
37
  # SecretSharing::Share.from_string "1-2"
37
38
  # # => #<SecretSharing::Share:0x0000000 @point=...>
38
39
  #
40
+ # @param share_string [String]
41
+ # @return [SecretSharing::Share] parsed share
39
42
  def self.from_string(share_string)
40
43
  x_string, y_string = share_string.split '-'
41
44
  point = Point.new x_string.to_i, y_string.to_i(16)
42
- Share.new(point)
45
+ new(point)
43
46
  end
44
47
  end
45
48
  end
@@ -1,4 +1,4 @@
1
1
  module SecretSharing
2
2
  # gem version
3
- VERSION = '0.0.2'
3
+ VERSION = '0.0.3'
4
4
  end
@@ -1,31 +1,113 @@
1
1
  require 'securerandom'
2
2
 
3
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::ASCIICharset.new
9
- encoded = charset.s_to_i(str)
10
- decoded = charset.i_to_s(encoded)
11
- expect(decoded).to eq(str)
12
- end
4
+ subject(:charset) { SecretSharing::Charset.new %w(a b c) }
5
+
6
+ describe '#initialize' do
7
+ it 'adds a non printable character in front of the charset' do
8
+ expect(charset.charset).to eq ["\u0000", 'a', 'b', 'c']
9
+ end
10
+
11
+ it 'makes the internal representation of the charset immutable' do
12
+ expect { charset.charset << 'x' }.to raise_error(RuntimeError)
13
+ end
14
+ end
15
+
16
+ describe '#i_to_s' do
17
+ it 'converts 0 to empty string' do
18
+ expect(charset.i_to_s(0)).to eq ''
19
+ end
20
+
21
+ it 'converts 1 to "a"' do
22
+ expect(charset.i_to_s(1)).to eq 'a'
23
+ end
24
+
25
+ it 'converts 6 to "ab"' do
26
+ expect(charset.i_to_s(6)).to eq 'ab'
27
+ end
28
+
29
+ it 'throws an error when trying to convert negative numbers' do
30
+ expect { charset.i_to_s(-1) }.to raise_error(
31
+ SecretSharing::Charset::NotPositiveInteger,
32
+ 'input must be a non-negative integer'
33
+ )
34
+ end
35
+
36
+ it 'throws an error when trying to convert something non-integer' do
37
+ expect { charset.i_to_s('error') }.to raise_error(
38
+ SecretSharing::Charset::NotPositiveInteger,
39
+ 'input must be a non-negative integer'
40
+ )
13
41
  end
42
+ end
14
43
 
15
- it 'should throw errors when characters that are not in charset' do
16
- charset = SecretSharing::Charset::ASCIICharset.new
17
- expect { charset.codepoint_to_char(130) }.to raise_error(ArgumentError)
18
- expect { charset.char_to_codepoint('ä') }.to raise_error(ArgumentError)
44
+ describe '#s_to_i' do
45
+ it 'converts "a" to 1' do
46
+ expect(charset.s_to_i('a')).to eq 1
19
47
  end
20
48
 
21
- it 'should throw an error when trying to convert negative numbers' do
22
- charset = SecretSharing::Charset::ASCIICharset.new
23
- expect { charset.i_to_s(-1) }.to raise_error(ArgumentError)
49
+ it 'converts "ab" to 6' do
50
+ expect(charset.s_to_i('ab')).to eq 6
24
51
  end
52
+ end
25
53
 
26
- it 'should throw an error when trying to convert something non-integer' do
27
- charset = SecretSharing::Charset::ASCIICharset.new
28
- expect { charset.i_to_s('error') }.to raise_error(ArgumentError)
54
+ describe '#codepoint_to_char' do
55
+ it 'returns "b" for 2' do
56
+ expect(charset.codepoint_to_char(2)).to eq 'b'
29
57
  end
58
+
59
+ it 'throws an error on an integer outside of the charset' do
60
+ expect { charset.codepoint_to_char(4) }.to raise_error(
61
+ SecretSharing::Charset::NotInCharset,
62
+ 'Codepoint 4 does not exist in charset'
63
+ )
64
+ end
65
+ end
66
+
67
+ describe '#char_to_codepoint' do
68
+ it 'returns 2 for "b"' do
69
+ expect(charset.char_to_codepoint('b')).to eq 2
70
+ end
71
+
72
+ it 'throws an error on a character that is not in the charset' do
73
+ expect { charset.char_to_codepoint('d') }.to raise_error(
74
+ SecretSharing::Charset::NotInCharset,
75
+ "Char \"d\" not part of the supported charset"
76
+ )
77
+ end
78
+ end
79
+
80
+ describe '#subset?' do
81
+ context 'the input is a subset' do
82
+ it 'returns true' do
83
+ expect(charset.subset?('ab')).to be true
84
+ end
85
+ end
86
+
87
+ context 'the input is not a subset' do
88
+ it 'returns false' do
89
+ expect(charset.subset?('abcdef')).to be false
90
+ end
91
+ end
92
+
93
+ context 'the input does not only contain unique characters' do
94
+ it 'is still a subset' do
95
+ expect(charset.subset?('aabc')).to be true
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ describe 'SecretSharing::Charset::ASCIICharset' do
102
+ it 'is a 128 character long charset' do
103
+ expect(SecretSharing::Charset::ASCIICharset.charset.length).to eq 128
104
+ end
105
+
106
+ it 'supports all printable characters in ASCII' do
107
+ all_chars = " !\"#$%&'()*+,-./0123456789:;<=>?@" \
108
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`" \
109
+ "abcdefghijklmnopqrstuvwxyz{|}~\n"
110
+ int = SecretSharing::Charset::ASCIICharset.s_to_i all_chars
111
+ expect(SecretSharing::Charset::ASCIICharset.i_to_s(int)).to eq all_chars
30
112
  end
31
113
  end
@@ -1,6 +1,31 @@
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>')
1
+ RSpec.describe SecretSharing::Point do
2
+ describe '#initialize' do
3
+ it 'sets the coordinates correctly' do
4
+ point = SecretSharing::Point.new(1, 2)
5
+ expect(point.x).to be 1
6
+ expect(point.y).to be 2
7
+ end
8
+ end
9
+
10
+ describe '.transpose' do
11
+ it 'returns an array consisting of two further arrays' do
12
+ result = SecretSharing::Point.transpose []
13
+ expect(result).to be_an Array
14
+ expect(result.length).to be 2
15
+ expect(result.first).to be_an Array
16
+ expect(result.last).to be_an Array
17
+ end
18
+
19
+ it 'puts all x values in the first and all y values in the second array' do
20
+ result = SecretSharing::Point.transpose([
21
+ SecretSharing::Point.new(1, 2),
22
+ SecretSharing::Point.new(3, 4),
23
+ SecretSharing::Point.new(5, 6)
24
+ ])
25
+ expect(result).to eq([
26
+ [1, 3, 5],
27
+ [2, 4, 6]
28
+ ])
29
+ end
5
30
  end
6
31
  end
@@ -1,22 +1,114 @@
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)
1
+ RSpec.describe SecretSharing::Polynomial do
2
+ describe '#initialize' do
3
+ it 'does not error when all coefficients are integers' do
4
+ SecretSharing::Polynomial.new [1, 2, 3]
5
+ end
6
+
7
+ it 'makes the coefficients immutable' do
8
+ polynomial = SecretSharing::Polynomial.new [1, 2, 3]
9
+ expect(polynomial.coefficients).to eq [1, 2, 3]
10
+ expect { polynomial.coefficients << 4 }.to raise_error(RuntimeError)
11
+ end
12
+
13
+ it 'raises an error when a coefficient is not an integer' do
14
+ expect { SecretSharing::Polynomial.new(['']) }.to raise_error(
15
+ ArgumentError,
16
+ 'One or more coefficents are not integers'
17
+ )
18
+ end
5
19
  end
6
20
 
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)
21
+ describe '#points' do
22
+ context '5 points for f(x)=x^4 + 2' do
23
+ subject(:polynomial) { SecretSharing::Polynomial.new([2, 0, 0, 0, 1]) }
24
+
25
+ it 'generates 5 points' do
26
+ expect(polynomial.points(5, 3).length).to eq 5
27
+ end
28
+
29
+ it 'generates points (1, 0), (2, 0) and (3, 2) for f(x)=x^4 + 2' do
30
+ points = polynomial.points(5, 7)
31
+ expect(points[0].x).to eq 1
32
+ expect(points[0].y).to eq 3
33
+ expect(points[1].x).to eq 2
34
+ expect(points[1].y).to eq 4
35
+ expect(points[2].x).to eq 3
36
+ expect(points[2].y).to eq 6
37
+ expect(points[3].x).to eq 4
38
+ expect(points[3].y).to eq 6
39
+ expect(points[4].x).to eq 5
40
+ expect(points[4].y).to eq 4
41
+ end
42
+ end
11
43
  end
12
44
 
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)
45
+ describe '.random' do
46
+ it 'errors when trying to generate a polynomial with negative degree' do
47
+ random_generator = -> { SecretSharing::Polynomial.random(-1, 1, 12) }
48
+ expect(&random_generator).to raise_error(
49
+ ArgumentError,
50
+ 'Degree must be a non-negative number'
51
+ )
52
+ end
53
+
54
+ it 'can create polynomials of degree 0' do
55
+ polynomial = SecretSharing::Polynomial.random(0, 5, 10)
56
+ expect(polynomial.coefficients).to eq [5]
57
+ end
58
+
59
+ it 'randomly generates coefficients' do
60
+ allow(SecureRandom).to receive(:random_number).and_return(3, 5, 2)
61
+ polynomial = SecretSharing::Polynomial.random(3, 5, 10)
62
+ expect(polynomial.coefficients).to eq [5, 3, 5 ,2]
63
+ end
64
+
65
+ it 'uses the intercept as the first coefficient' do
66
+ polynomial = SecretSharing::Polynomial.random(3, 5, 10)
67
+ expect(polynomial.coefficients.first).to eq 5
68
+ end
69
+
70
+ it 'creates a polynomial with degree as number of coefficients' do
71
+ polynomial = SecretSharing::Polynomial.random(3, 3, 10)
72
+ expect(polynomial.coefficients.length).to eq 4
73
+ end
16
74
  end
17
75
 
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)
76
+ describe '.points_from_secret' do
77
+ it 'generates points according to the num_points parameter' do
78
+ points = SecretSharing::Polynomial.points_from_secret(10, 2, 4)
79
+ expect(points.length).to eq 4
80
+ end
81
+
82
+ it 'requires threshold points to reconstruct' do
83
+ points = SecretSharing::Polynomial.points_from_secret(234234, 2, 3)
84
+ points = points[0, 2]
85
+ secret = SecretSharing::Polynomial.modular_lagrange_interpolation(points)
86
+ expect(secret).to eq 234234
87
+ end
88
+
89
+ it 'errors when the secret is to long' do
90
+ secret = 2**10_000
91
+ block = -> { SecretSharing::Polynomial.points_from_secret(secret, 2, 3) }
92
+ expect(&block).to raise_error(
93
+ ArgumentError,
94
+ 'Secret is too long'
95
+ )
96
+ end
97
+
98
+ it 'errors when the point threshold is too small' do
99
+ block = -> { SecretSharing::Polynomial.points_from_secret(123, 1, 3) }
100
+ expect(&block).to raise_error(
101
+ ArgumentError,
102
+ 'Threshold must be at least 2'
103
+ )
104
+ end
105
+
106
+ it 'errors when the point threshold is larger than total points' do
107
+ block = -> { SecretSharing::Polynomial.points_from_secret(123, 3, 2) }
108
+ expect(&block).to raise_error(
109
+ ArgumentError,
110
+ 'Threshold must be less than the total number of points'
111
+ )
112
+ end
21
113
  end
22
114
  end
@@ -1,7 +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)
1
+ RSpec.describe SecretSharing::Prime do
2
+ describe '#get_large_enough_prime' do
3
+ it 'calc the correct prime' do
4
+ expect(SecretSharing::Prime.large_enough_prime(4)).to eq(7)
5
5
  end
6
6
  end
7
7
  end
@@ -1,6 +1,6 @@
1
1
  require 'securerandom'
2
2
 
3
- describe SecretSharing do
3
+ RSpec.describe SecretSharing do
4
4
  it 'should encrypt and decrypt correctly' do
5
5
  50.times do
6
6
  secret = SecureRandom.base64(130)
@@ -9,4 +9,32 @@ describe SecretSharing do
9
9
  expect(SecretSharing.recover_secret(shares[0..1])).to eq(secret)
10
10
  end
11
11
  end
12
+
13
+ context 'threshold 2 out of 4 shares' do
14
+ subject(:shares) { SecretSharing.split_secret('secret', 2, 4) }
15
+
16
+ it 'generates 4 shares' do
17
+ expect(shares.length). to eq 4
18
+ end
19
+
20
+ it 'cannot reconstruct with only one share' do
21
+ expect(SecretSharing.recover_secret(shares[0, 1])).not_to eq 'secret'
22
+ end
23
+
24
+ it 'can reconstruct the secret with any combination of 2 shares' do
25
+ shares.permutation(2).each do |share_combination|
26
+ expect(SecretSharing.recover_secret(share_combination)).to eq 'secret'
27
+ end
28
+ end
29
+
30
+ it 'can reconstruct the secret with any combination of 3 shares' do
31
+ shares.permutation(3).each do |share_combination|
32
+ expect(SecretSharing.recover_secret(share_combination)).to eq 'secret'
33
+ end
34
+ end
35
+
36
+ it 'can reconstruct the secret with all shares' do
37
+ expect(SecretSharing.recover_secret(shares)).to eq 'secret'
38
+ end
39
+ end
12
40
  end
@@ -1,13 +1,43 @@
1
- describe SecretSharing::Share do
2
- it 'can create an instance from a string' do
3
- share = SecretSharing::Share.from_string('1-2')
4
- point = share.point
5
- expect(point.x).to be 1
6
- expect(point.y).to be 2
1
+ RSpec.describe SecretSharing::Share do
2
+ describe '.from_string' do
3
+ it 'creates an instance from a string' do
4
+ share = SecretSharing::Share.from_string('1-2')
5
+ point = share.point
6
+ expect(point.x).to be 1
7
+ expect(point.y).to be 2
8
+ end
9
+
10
+ it 'correctly converts the y value from hex to integer' do
11
+ share = SecretSharing::Share.from_string('1-1b')
12
+ point = share.point
13
+ expect(point.x).to be 1
14
+ expect(point.y).to be 27
15
+ end
16
+
17
+ it 'correctly converts paddings' do
18
+ share = SecretSharing::Share.from_string('01-1b')
19
+ point = share.point
20
+ expect(point.x).to be 1
21
+ expect(point.y).to be 27
22
+ end
7
23
  end
8
24
 
9
- it 'correctly stringifies' do
10
- point = SecretSharing::Point.new(1, 2)
11
- expect(SecretSharing::Share.new(point).to_s).to eq('1-2')
25
+ describe '#to_s' do
26
+ it 'stringifies the share by concatenating the x and y value' do
27
+ point = SecretSharing::Point.new(1, 2)
28
+ expect(SecretSharing::Share.new(point).to_s(1)).to eq('1-2')
29
+ end
30
+
31
+ it 'converts the y value to hex' do
32
+ point = SecretSharing::Point.new(1, 27)
33
+ expect(SecretSharing::Share.new(point).to_s(1)).to eq('1-1b')
34
+ end
35
+
36
+ context 'a x point length is specified' do
37
+ it 'adds 0 padding so all shares are equal length' do
38
+ point = SecretSharing::Point.new(1, 27)
39
+ expect(SecretSharing::Share.new(point).to_s(2)).to eq('01-1b')
40
+ end
41
+ end
12
42
  end
13
43
  end
@@ -27,6 +27,8 @@ if ENV['CI']
27
27
  Coveralls.wear!
28
28
  end
29
29
 
30
+ require 'timeout'
31
+
30
32
  require 'secret_sharing'
31
33
 
32
34
  RSpec.configure do |config|
@@ -52,4 +54,8 @@ RSpec.configure do |config|
52
54
  # `true` in RSpec 4.
53
55
  mocks.verify_partial_doubles = true
54
56
  end
57
+
58
+ config.around :each do |block|
59
+ Timeout.timeout(1, &block)
60
+ end
55
61
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secret_sharing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - flower-pot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-24 00:00:00.000000000 Z
11
+ date: 2015-04-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Divide, share and reconstruct secrets.
14
14
  email:
@@ -17,11 +17,11 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
- - ".gitignore"
21
- - ".rspec"
22
- - ".rubocop.yml"
23
- - ".travis.yml"
24
- - ".yardops"
20
+ - .gitignore
21
+ - .rspec
22
+ - .rubocop.yml
23
+ - .travis.yml
24
+ - .yardops
25
25
  - Gemfile
26
26
  - LICENSE
27
27
  - README.md
@@ -51,17 +51,17 @@ require_paths:
51
51
  - lib
52
52
  required_ruby_version: !ruby/object:Gem::Requirement
53
53
  requirements:
54
- - - ">="
54
+ - - ! '>='
55
55
  - !ruby/object:Gem::Version
56
56
  version: '0'
57
57
  required_rubygems_version: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  requirements: []
63
63
  rubyforge_project:
64
- rubygems_version: 2.4.6
64
+ rubygems_version: 2.4.5
65
65
  signing_key:
66
66
  specification_version: 4
67
67
  summary: Ruby implementation of sharmir's secret sharing
@@ -73,4 +73,3 @@ test_files:
73
73
  - spec/secret_sharing_spec.rb
74
74
  - spec/share_spec.rb
75
75
  - spec/spec_helper.rb
76
- has_rdoc: