ecdsa 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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