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 +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
|
-
[](https://travis-ci.org/DavidEGrayson/ruby_ecdsa)
|
2
|
-
|
3
1
|
# ECDSA gem for Ruby
|
4
2
|
|
3
|
+
[](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
|