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 +4 -4
- data/Gemfile +2 -1
- data/README.md +9 -9
- data/lib/ecdsa.rb +15 -19
- data/lib/ecdsa/group.rb +27 -0
- data/lib/ecdsa/point.rb +10 -0
- data/lib/ecdsa/prime_field.rb +85 -6
- data/lib/ecdsa/recover_public_key.rb +61 -0
- data/lib/ecdsa/sign.rb +2 -6
- data/lib/ecdsa/verify.rb +8 -11
- data/lib/ecdsa/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25e8c83f3ce99fc8aa59227d35d585c6e9a6243d
|
4
|
+
data.tar.gz: 075eae949de245595465e56bc88cf343a96963a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc8301baedb1e03857302738d7573c2a9cf3a521a6f396529879a190190c2e17f2074a2105277d7296d8fe8535e8322fd51d0a763322f4018d74ac0e171eb024
|
7
|
+
data.tar.gz: a85fdcc62172795e57e3926e14a7c910568b68672cf51a753aa53bd46018eabddb1e8cc4800319fedfb878f0f36d9aa10c2168447ea96918119840e000ccd0d1
|
data/Gemfile
CHANGED
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
|
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
|
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).
|
data/lib/ecdsa.rb
CHANGED
@@ -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.
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
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
|
data/lib/ecdsa/group.rb
CHANGED
@@ -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)
|
data/lib/ecdsa/point.rb
CHANGED
@@ -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
|
#
|
data/lib/ecdsa/prime_field.rb
CHANGED
@@ -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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
data/lib/ecdsa/sign.rb
CHANGED
@@ -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
|
data/lib/ecdsa/verify.rb
CHANGED
@@ -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
|
-
#
|
55
|
-
|
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
|
data/lib/ecdsa/version.rb
CHANGED
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.
|
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-
|
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
|