ecdsa 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 112522081e19ec7215da474caa4ee348b0ccca48
4
- data.tar.gz: b7ce849fcd146350259a96830992a5e68a020d88
3
+ metadata.gz: 25e8c83f3ce99fc8aa59227d35d585c6e9a6243d
4
+ data.tar.gz: 075eae949de245595465e56bc88cf343a96963a4
5
5
  SHA512:
6
- metadata.gz: 4112fc44b63041825d083a5ac7fd510a8b7ba800183c89d137e7feed2656fbeca69177fb7dce11bbc44ece676540ae9970043fedbe38ce5c4dcd4add30187b15
7
- data.tar.gz: 4d9695b86a32f46719dd99e4420d7b4a4c83aa6ba142dbf830d27a6f5be4af63e7208ff674bb0ceba75cfb55db23c863de2a02e98c3ff2812fa59bcdf2ed6961
6
+ metadata.gz: cc8301baedb1e03857302738d7573c2a9cf3a521a6f396529879a190190c2e17f2074a2105277d7296d8fe8535e8322fd51d0a763322f4018d74ac0e171eb024
7
+ data.tar.gz: a85fdcc62172795e57e3926e14a7c910568b68672cf51a753aa53bd46018eabddb1e8cc4800319fedfb878f0f36d9aa10c2168447ea96918119840e000ccd0d1
data/Gemfile CHANGED
@@ -2,7 +2,8 @@ source 'https://rubygems.org/'
2
2
 
3
3
  gem 'rspec'
4
4
 
5
- gem 'rubocop', '0.19.1'
5
+ gem 'rubocop', '0.20.1'
6
+ gem 'simplecov'
6
7
 
7
8
  gem 'yard'
8
9
  gem 'markdown'
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
- [![Build Status](https://travis-ci.org/DavidEGrayson/ruby_ecdsa.svg?branch=master)](https://travis-ci.org/DavidEGrayson/ruby_ecdsa)
2
-
3
1
  # ECDSA gem for Ruby
4
2
 
3
+ [![Build Status](https://travis-ci.org/DavidEGrayson/ruby_ecdsa.svg?branch=master)](https://travis-ci.org/DavidEGrayson/ruby_ecdsa)
4
+
5
5
  This gem implements the Elliptic Curve Digital Signature Algorithm (ECDSA)
6
6
  almost entirely in pure Ruby. It aims to be easier to use and easier to
7
7
  understand than Ruby's
@@ -34,10 +34,6 @@ This gem is hosted at the [DavidEGrayson/ruby_ecdsa github repository](https://g
34
34
 
35
35
  - This gem only supports fields of integers modulo a prime number
36
36
  (_F<sub>p</sub>_). ECDSA's characteristic 2 fields are not supported.
37
- - This gem can only compute square roots in prime fields over a prime _p_
38
- that is one less than a multiple of 4.
39
- Computing a square root is required for parsing public keys stored in
40
- compressed form.
41
37
  - The algorithms have not been optimized for speed, and will probably never be,
42
38
  because that would hinder the goal of helping people understand ECDSA.
43
39
 
@@ -84,7 +80,7 @@ The `public_key` object produced by the code above is an `ECDSA::Point` object.
84
80
  ## Encoding a public key as a binary string
85
81
 
86
82
  Assuming that you have an `ECDSA::Point` object representing the public key,
87
- you can convert it to the standard binary format defined in SEC2 with this code:
83
+ you can convert it to the standard binary format defined in SEC1 with this code:
88
84
 
89
85
  ```ruby
90
86
  public_key_string = ECDSA::Format::PointOctetString.encode(public_key, compression: true)
@@ -98,7 +94,7 @@ This code returns a binary string.
98
94
 
99
95
  ## Decoding a public key from a binary string
100
96
 
101
- To decode a SEC2 octet string, you can use the code below. The `group` object
97
+ To decode a SEC1 octet string, you can use the code below. The `group` object
102
98
  is assumed to be an `ECDSA::Group`.
103
99
 
104
100
  ```ruby
@@ -169,4 +165,8 @@ puts "valid: #{valid}"
169
165
  ## Supported platforms
170
166
 
171
167
  This library should run on any Ruby interpreter that is compatible with Ruby 1.9.3.
172
- It has been tested on JRuby 1.7.11 and MRI.
168
+ It has been tested on JRuby 1.7.11 and MRI.
169
+
170
+ ## Documentation
171
+
172
+ For complete documentation, see the [ECDSA page on RubyDoc.info](http://rubydoc.info/gems/ecdsa).
@@ -1,7 +1,8 @@
1
1
  require_relative 'ecdsa/group'
2
2
  require_relative 'ecdsa/signature'
3
- require_relative 'ecdsa/verify'
4
3
  require_relative 'ecdsa/sign'
4
+ require_relative 'ecdsa/verify'
5
+ require_relative 'ecdsa/recover_public_key'
5
6
  require_relative 'ecdsa/format'
6
7
  require_relative 'ecdsa/version'
7
8
 
@@ -28,26 +29,21 @@ module ECDSA
28
29
  end
29
30
 
30
31
  # This method is NOT part of the public API of the ECDSA gem.
31
- def self.convert_digest_to_integer(digest)
32
- case digest
33
- when Integer then digest
34
- when String then Format::IntegerOctetString.decode(digest)
35
- else raise "Invalid digest: #{digest.inspect}"
36
- end
37
- end
32
+ def self.normalize_digest(digest, bit_length)
33
+ if digest.is_a?(String)
34
+ digest = digest.dup.force_encoding('BINARY')
35
+ digest_bit_length = digest.size * 8
36
+ num = Format::IntegerOctetString.decode(digest)
38
37
 
39
- # This method is NOT part of the public API of the ECDSA gem.
40
- def self.leftmost_bits(n, bit_length)
41
- if n >= (1 << (8 * bit_length))
42
- raise 'Have not yet written code to handle this case'
38
+ if digest_bit_length <= bit_length
39
+ num
40
+ else
41
+ num >> (digest_bit_length - bit_length)
42
+ end
43
+ elsif digest.is_a?(Integer)
44
+ digest
43
45
  else
44
- n
46
+ raise ArgumentError, 'Digest must be a string or integer.'
45
47
  end
46
48
  end
47
-
48
- # This method is NOT part of the public API of the ECDSA gem.
49
- def self.normalize_digest(digest, bit_length)
50
- digest_num = convert_digest_to_integer(digest)
51
- leftmost_bits(digest_num, bit_length)
52
- end
53
49
  end
@@ -1,6 +1,9 @@
1
1
  require_relative 'prime_field'
2
2
  require_relative 'point'
3
3
 
4
+ # TODO: we keep using this abstraction called the 'point_field' all over the place.
5
+ # Figure out what it is really called and make it come from a method on Group.
6
+
4
7
  module ECDSA
5
8
  class Group
6
9
  # The name of the group.
@@ -17,6 +20,11 @@ module ECDSA
17
20
  # @return (Order)
18
21
  attr_reader :order
19
22
 
23
+ # The cofactor of the group.
24
+ # This is the number of points on the curve divided by the number of points
25
+ # in the group generated by the generator.
26
+ attr_reader :cofactor
27
+
20
28
  # The a parameter in the curve equation (*y^2 = x^3 + ax + b*).
21
29
  # @option opts :a (Integer)
22
30
  attr_reader :param_a
@@ -105,6 +113,25 @@ module ECDSA
105
113
  point.infinity? or point_satisfies_equation?(point)
106
114
  end
107
115
 
116
+ # Returns true if the point is not infinity, it is a solution to the curve's
117
+ # defining equation, and it is a multiple of G. This process is defined in
118
+ # SEC1 2.0, Section 3.2.2.1: Elliptic Curve Public Key Partial Validation Primitive
119
+ def valid_public_key?(point)
120
+ return false if point.group != self
121
+ return false if point.infinity?
122
+ return false if !point_satisfies_equation?(point)
123
+ point.multiply_by_scalar(order).infinity?
124
+ end
125
+
126
+ # Returns true if the point is not infinity and it is a solution to
127
+ # the curve's defining equation. This is defined in
128
+ # SEC1 2.0, Section 3.2.3.1: Elliptic Curve Public Key Partial Validation Primitive
129
+ def partially_valid_public_key?(point)
130
+ return false if point.group != self
131
+ return false if point.infinity?
132
+ point_satisfies_equation?(point)
133
+ end
134
+
108
135
  # Given the x coordinate of a point, finds all possible corresponding y coordinates.
109
136
  #
110
137
  # @return (Array)
@@ -138,6 +138,16 @@ module ECDSA
138
138
  eql?(other)
139
139
  end
140
140
 
141
+ # Returns a hash for this point so it can be stored in hash tables
142
+ # with the expected behavior. Two points that have the same coordinates
143
+ # and are on the same curve are equal, so they should not get separate
144
+ # spots in a hash table.
145
+ #
146
+ # @return (Integer)
147
+ def hash
148
+ [group, x, y].hash
149
+ end
150
+
141
151
  # Returns true if this instance represents the infinity point (the additive
142
152
  # identity of the group).
143
153
  #
@@ -88,28 +88,107 @@ module ECDSA
88
88
  end
89
89
 
90
90
  # Finds all possible square roots of the given field element.
91
- # Currently this method only supports fields where the prime is one
92
- # less than a multiple of four.
93
91
  #
94
92
  # @param n (Integer)
95
93
  # @return (Array) A sorted array of numbers whose square is equal to `n`.
96
94
  def square_roots(n)
97
95
  raise ArgumentError, "Not a member of the field: #{n}." if !include?(n)
98
- if (prime % 4) == 3
99
- square_roots_for_p_3_mod_4(n)
100
- else
101
- raise NotImplementedError, 'Square root is only implemented in fields where the prime is equal to 3 mod 4.'
96
+ case
97
+ when prime == 2 then [n]
98
+ when (prime % 4) == 3 then square_roots_for_p_3_mod_4(n)
99
+ when (prime % 8) == 5 then square_roots_for_p_5_mod_8(n)
100
+ else square_roots_default(n)
101
+ end
102
+ end
103
+
104
+ # This method is NOT part of the public API of the ECDSA gem.
105
+ def self.factor_out_twos(x)
106
+ e = 0
107
+ while x.even?
108
+ x /= 2
109
+ e += 1
102
110
  end
111
+ [e, x]
112
+ end
113
+
114
+ # This method is NOT part of the public API of the ECDSA gem.
115
+ #
116
+ # Algorithm algorithm 2.149 from http://cacr.uwaterloo.ca/hac/
117
+ def self.jacobi(n, p)
118
+ raise 'Jacobi symbol is not defined for primes less than 3.' if p < 3
119
+
120
+ n = n % p
121
+
122
+ return 0 if n == 0 # Step 1
123
+ return 1 if n == 1 # Step 2
124
+
125
+ e, n1 = factor_out_twos n # Step 3
126
+ s = (e.even? || [1, 7].include?(p % 8)) ? 1 : -1 # Step 4
127
+ s = -s if (p % 4) == 3 && (n1 % 4) == 3 # Step 5
128
+
129
+ # Step 6 and 7
130
+ return s if n1 == 1
131
+ s * jacobi(p, n1)
103
132
  end
104
133
 
105
134
  private
106
135
 
136
+ def jacobi(n)
137
+ self.class.send(:jacobi, n, prime)
138
+ end
139
+
107
140
  # This is Algorithm 1 from http://math.stanford.edu/~jbooher/expos/sqr_qnr.pdf
141
+ # It is also Algorithm 3.36 from http://cacr.uwaterloo.ca/hac/
108
142
  # The algorithm assumes that its input actually does have a square root.
109
143
  # To get around that, we double check the answer after running the algorithm to make
110
144
  # sure it works.
111
145
  def square_roots_for_p_3_mod_4(n)
112
146
  candidate = power n, (prime + 1) / 4
147
+ square_roots_given_candidate n, candidate
148
+ end
149
+
150
+ # This is Algorithm 3.37 from http://cacr.uwaterloo.ca/hac/
151
+ def square_roots_for_p_5_mod_8(n)
152
+ case power n, (prime - 1) / 4
153
+ when 1
154
+ candidate = power n, (prime + 3) / 8
155
+ when prime - 1
156
+ candidate = mod 2 * n * power(4 * n, (prime - 5) / 8)
157
+ else
158
+ # I think this can happen because we are not checking the Jacobi.
159
+ return []
160
+ end
161
+ square_roots_given_candidate n, candidate
162
+ end
163
+
164
+ # This is Algorithm 3.34 from http://cacr.uwaterloo.ca/hac/
165
+ # It has no limitations except prime must be at least three, so we could
166
+ # remove all the other square root algorithms if we wanted to.
167
+ def square_roots_default(n)
168
+ return [0] if n == 0
169
+ return [] if jacobi(n) == -1 # Step 1
170
+
171
+ # Step 2, except we don't want to use randomness.
172
+ b = (1...prime).find { |i| jacobi(i) == -1 }
173
+
174
+ s, t = self.class.factor_out_twos(prime - 1) # Step 3
175
+ n_inv = inverse(n) # Step 4
176
+
177
+ # Step 5
178
+ c = power b, t
179
+ r = power n, (t + 1) / 2
180
+
181
+ # Step 6
182
+ (1...s).each do |i|
183
+ d = power r * r * n_inv, 1 << (s - i - 1)
184
+ r = mod r * c if d == prime - 1
185
+ c = square c
186
+ end
187
+
188
+ square_roots_given_candidate n, r
189
+ end
190
+
191
+ def square_roots_given_candidate(n, candidate)
113
192
  return [] if square(candidate) != n
114
193
  [candidate, mod(-candidate)].uniq.sort
115
194
  end
@@ -0,0 +1,61 @@
1
+ module ECDSA
2
+ # Recovers the set of possible public keys from a {Signature} and the digest
3
+ # that it signs.
4
+ #
5
+ # If you do not pass a block to `recover_public_key` then it returns an
6
+ # Enumerator that will lazily find more public keys when needed. If you
7
+ # are going to iterate through the enumerator more than once, you should
8
+ # probably convert it to an array first with `to_a` to save CPU time.
9
+ #
10
+ # If you pass a block, it will yield the public keys to the block one at a
11
+ # time as it finds them.
12
+ #
13
+ # This is better than just returning an array of all possibilities, because
14
+ # it allows the caller to stop the algorithm when the desired public key has
15
+ # been found, saving CPU time.
16
+ #
17
+ # This algorithm comes from Section 4.1.6 of [SEC1 2.0](http://www.secg.org/download/aid-780/sec1-v2.pdf)
18
+ #
19
+ # @param group (Group)
20
+ # @param digest (String or Integer)
21
+ # @param signature (Signature)
22
+ def self.recover_public_key(group, digest, signature)
23
+ return enum_for(:recover_public_key, group, digest, signature) if !block_given?
24
+
25
+ digest = normalize_digest(digest, group.bit_length)
26
+
27
+ each_possible_temporary_public_key(group, digest, signature) do |point|
28
+ yield calculate_public_key(group, digest, signature, point)
29
+ end
30
+
31
+ nil
32
+ end
33
+
34
+ private
35
+
36
+ def self.each_possible_temporary_public_key(group, digest, signature)
37
+ # Instead of using the cofactor as the iteration limit as specified in SEC1,
38
+ # we just iterate until x is too large to fit in the underlying field.
39
+ # That way we don't have to know the cofactor of the group.
40
+ signature.r.step(group.field.prime - 1, group.order) do |x|
41
+ group.solve_for_y(x).each do |y|
42
+ point = group.new_point [x, y]
43
+ yield point if point.multiply_by_scalar(group.order).infinity?
44
+ end
45
+ end
46
+ end
47
+
48
+ # Assuming that we know the public key corresponding to the (random) temporary
49
+ # private key used during signing, this method tells us what the actual
50
+ # public key was.
51
+ def self.calculate_public_key(group, digest, signature, temporary_public_key)
52
+ point_field = PrimeField.new(group.order)
53
+
54
+ # public key = (tempPubKey * s - G * e) / r
55
+ rs = temporary_public_key.multiply_by_scalar(signature.s)
56
+ ge = group.generator.multiply_by_scalar(digest)
57
+ r_inv = point_field.inverse(signature.r)
58
+
59
+ rs.add_to_point(ge.negate).multiply_by_scalar(r_inv)
60
+ end
61
+ end
@@ -30,6 +30,7 @@ module ECDSA
30
30
  # Steps 2 and 3
31
31
  point_field = PrimeField.new(group.order)
32
32
  r = point_field.mod(r_point.x)
33
+ return nil if r.zero?
33
34
 
34
35
  # Step 4, calculating the hash, was already performed by the caller.
35
36
 
@@ -38,12 +39,7 @@ module ECDSA
38
39
 
39
40
  # Step 6
40
41
  s = point_field.mod(point_field.inverse(temporary_key) * (e + r * private_key))
41
-
42
- if s.zero?
43
- # We need to go back to step 1, so the caller should generate another
44
- # random number temporary_key and try again.
45
- return nil
46
- end
42
+ return nil if s.zero?
47
43
 
48
44
  Signature.new r, s
49
45
  end
@@ -31,10 +31,10 @@ module ECDSA
31
31
  field = group.field
32
32
 
33
33
  # Step 1: r and s must be in the field and non-zero
34
- raise InvalidSignatureError, 'r is not in the field.' if !field.include?(signature.r)
35
- raise InvalidSignatureError, 's is not in the field.' if !field.include?(signature.s)
36
- raise InvalidSignatureError, 'r is zero.' if signature.r.zero?
37
- raise InvalidSignatureError, 's is zero.' if signature.s.zero?
34
+ raise InvalidSignatureError, 'Invalid signature: r is not in the field.' if !field.include?(signature.r)
35
+ raise InvalidSignatureError, 'Invalid signature: s is not in the field.' if !field.include?(signature.s)
36
+ raise InvalidSignatureError, 'Invalid signature: r is zero.' if signature.r.zero?
37
+ raise InvalidSignatureError, 'Invalid signature: s is zero.' if signature.s.zero?
38
38
 
39
39
  # Step 2 was already performed when the digest of the message was computed.
40
40
 
@@ -49,16 +49,13 @@ module ECDSA
49
49
 
50
50
  # Step 5
51
51
  r = group.generator.multiply_by_scalar(u1).add_to_point public_key.multiply_by_scalar(u2)
52
- raise InvalidSignatureError, 'r is infinity in step 5.' if r.infinity?
52
+ raise InvalidSignatureError, 'Invalid signature: r is infinity in step 5.' if r.infinity?
53
53
 
54
- # Step 6
55
- xr = r.x
56
-
57
- # Step 7
58
- v = point_field.mod xr
54
+ # Steps 6 and 7
55
+ v = point_field.mod r.x
59
56
 
60
57
  # Step 8
61
- raise InvalidSignatureError, 'v does not equal r.' if v != signature.r
58
+ raise InvalidSignatureError, 'Invalid signature: v does not equal r.' if v != signature.r
62
59
 
63
60
  true
64
61
  end
@@ -1,3 +1,3 @@
1
1
  module ECDSA
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecdsa
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Grayson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-06 00:00:00.000000000 Z
11
+ date: 2014-04-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: davidegrayson@gmail.com
@@ -45,6 +45,7 @@ files:
45
45
  - lib/ecdsa/group.rb
46
46
  - lib/ecdsa/point.rb
47
47
  - lib/ecdsa/prime_field.rb
48
+ - lib/ecdsa/recover_public_key.rb
48
49
  - lib/ecdsa/sign.rb
49
50
  - lib/ecdsa/signature.rb
50
51
  - lib/ecdsa/verify.rb