ecdsa 0.1.5 → 1.0.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 +9 -1
- data/README.md +154 -28
- data/lib/ecdsa.rb +7 -13
- data/lib/ecdsa/format/field_element_octet_string.rb +17 -5
- data/lib/ecdsa/format/integer_octet_string.rb +18 -11
- data/lib/ecdsa/format/point_octet_string.rb +25 -6
- data/lib/ecdsa/format/signature_der_string.rb +13 -3
- data/lib/ecdsa/group.rb +58 -22
- data/lib/ecdsa/point.rb +71 -16
- data/lib/ecdsa/prime_field.rb +48 -12
- data/lib/ecdsa/sign.rb +27 -14
- data/lib/ecdsa/signature.rb +9 -0
- data/lib/ecdsa/verify.rb +20 -3
- data/lib/ecdsa/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 112522081e19ec7215da474caa4ee348b0ccca48
|
4
|
+
data.tar.gz: b7ce849fcd146350259a96830992a5e68a020d88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4112fc44b63041825d083a5ac7fd510a8b7ba800183c89d137e7feed2656fbeca69177fb7dce11bbc44ece676540ae9970043fedbe38ce5c4dcd4add30187b15
|
7
|
+
data.tar.gz: 4d9695b86a32f46719dd99e4420d7b4a4c83aa6ba142dbf830d27a6f5be4af63e7208ff674bb0ceba75cfb55db23c863de2a02e98c3ff2812fa59bcdf2ed6961
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,46 +1,172 @@
|
|
1
|
+
[](https://travis-ci.org/DavidEGrayson/ruby_ecdsa)
|
2
|
+
|
1
3
|
# ECDSA gem for Ruby
|
2
4
|
|
3
|
-
This gem implements the Elliptic Curve Digital Signature Algorithm (ECDSA)
|
4
|
-
It aims to be easier to use and easier to
|
5
|
-
|
6
|
-
|
5
|
+
This gem implements the Elliptic Curve Digital Signature Algorithm (ECDSA)
|
6
|
+
almost entirely in pure Ruby. It aims to be easier to use and easier to
|
7
|
+
understand than Ruby's
|
8
|
+
[OpenSSL EC support](http://www.ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL/PKey/EC.html).
|
9
|
+
This gem does use OpenSSL but it only uses it to decode and encode ASN1 strings
|
10
|
+
for ECDSA signatures. All cryptographic calculations are done in pure Ruby.
|
7
11
|
|
8
|
-
The main classes of this gem are `ECDSA::Group`, `ECDSA::Point`, and
|
9
|
-
These classes operate on Ruby integers and do not deal at
|
10
|
-
Encoding and decoding of binary formats is solely
|
12
|
+
The main classes of this gem are `ECDSA::Group`, `ECDSA::Point`, and
|
13
|
+
`ECDSA::Signature`. These classes operate on Ruby integers and do not deal at
|
14
|
+
all with binary formatting. Encoding and decoding of binary formats is solely
|
15
|
+
handled by classes under the `ECDSA::Format` module.
|
11
16
|
|
12
|
-
You can enter your own curve parameters by instantiating a new `ECDSA::Group`
|
13
|
-
use a pre-existing group object such as
|
14
|
-
The pre-existing groups can be seen in the
|
15
|
-
|
17
|
+
You can enter your own curve parameters by instantiating a new `ECDSA::Group`
|
18
|
+
object or you can use a pre-existing group object such as
|
19
|
+
`ECDSA::Group::Secp256k1`. The pre-existing groups can be seen in the
|
20
|
+
`lib/ecdsa/group` folder, and include all the curves defined in
|
21
|
+
[SEC2](http://www.secg.org/collateral/sec2_final.pdf) and
|
22
|
+
[NIST's Recommended Elliptic Curves for Federal Government Use](http://csrc.nist.gov/groups/ST/toolkit/documents/dss/NISTReCur.pdf).
|
16
23
|
|
17
24
|
This gem does not use any randomness; all the algorithms are deterministic.
|
18
|
-
In order to sign a message, you must generate a secure random number _k_
|
19
|
-
and the order of the group and pass it as an argument to `ECDSA.sign`.
|
20
|
-
You should take measures to ensure that you never use the same random number to
|
21
|
-
two different messages, or else it would be easy for someone to compute
|
22
|
-
private key from those two signatures.
|
25
|
+
In order to sign a message, you must generate a secure random number _k_
|
26
|
+
between 0 and the order of the group and pass it as an argument to `ECDSA.sign`.
|
27
|
+
You should take measures to ensure that you never use the same random number to
|
28
|
+
sign two different messages, or else it would be easy for someone to compute
|
29
|
+
your private key from those two signatures.
|
23
30
|
|
24
31
|
This gem is hosted at the [DavidEGrayson/ruby_ecdsa github repository](https://github.com/DavidEGrayson/ruby_ecdsa).
|
25
32
|
|
26
33
|
## Current limitations
|
27
34
|
|
28
|
-
- This gem only supports fields of integers modulo a prime number
|
29
|
-
ECDSA's characteristic 2 fields are not supported.
|
35
|
+
- This gem only supports fields of integers modulo a prime number
|
36
|
+
(_F<sub>p</sub>_). ECDSA's characteristic 2 fields are not supported.
|
30
37
|
- This gem can only compute square roots in prime fields over a prime _p_
|
31
38
|
that is one less than a multiple of 4.
|
32
|
-
Computing a square root is required for parsing public keys stored in
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
would hinder the goal of helping people understand ECDSA.
|
39
|
+
Computing a square root is required for parsing public keys stored in
|
40
|
+
compressed form.
|
41
|
+
- The algorithms have not been optimized for speed, and will probably never be,
|
42
|
+
because that would hinder the goal of helping people understand ECDSA.
|
37
43
|
|
38
|
-
This gem was not written by a cryptography expert and has not been carefully
|
39
|
-
It is provided "as is" and it is the user's responsibility to make
|
40
|
-
suitable for the desired purpose.
|
44
|
+
This gem was not written by a cryptography expert and has not been carefully
|
45
|
+
checked. It is provided "as is" and it is the user's responsibility to make
|
46
|
+
sure it will be suitable for the desired purpose.
|
41
47
|
|
42
48
|
## Installation
|
43
49
|
|
44
|
-
This library is
|
50
|
+
This library is distributed as a gem named [ecdsa](https://rubygems.org/gems/ecdsa)
|
51
|
+
at RubyGems.org. To install it, run:
|
52
|
+
|
53
|
+
gem install ecdsa
|
54
|
+
|
55
|
+
## Generating a private key
|
56
|
+
|
57
|
+
An ECDSA private key is a random number between 1 and the order of the group.
|
58
|
+
If you trust the `SecureRandom` class provided by your Ruby implementation, you
|
59
|
+
could generate a private key using this code:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
require 'ecdsa'
|
63
|
+
require 'securerandom'
|
64
|
+
group = ECDSA::Group::Secp256k1
|
65
|
+
private_key = 1 + SecureRandom.random_number(group.order - 1)
|
66
|
+
puts 'private key: %#x' % private_key
|
67
|
+
```
|
68
|
+
|
69
|
+
## Computing the public key for a private key
|
70
|
+
|
71
|
+
The public key consists of the coordinates of the point that is computed by
|
72
|
+
multiplying the generator point of the curve with the private key.
|
73
|
+
This is equivalent to adding the generator to itself `private_key` times.
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
public_key = group.generator.multiply_by_scalar(private_key)
|
77
|
+
puts 'public key: '
|
78
|
+
puts ' x: %#x' % public_key.x
|
79
|
+
puts ' y: %#x' % public_key.y
|
80
|
+
```
|
81
|
+
|
82
|
+
The `public_key` object produced by the code above is an `ECDSA::Point` object.
|
83
|
+
|
84
|
+
## Encoding a public key as a binary string
|
85
|
+
|
86
|
+
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:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
public_key_string = ECDSA::Format::PointOctetString.encode(public_key, compression: true)
|
91
|
+
```
|
92
|
+
|
93
|
+
Setting the `compression` option to `true` decreases the size of the string by
|
94
|
+
almost 50% by only including one bit of the Y coordinate. The other bits of the
|
95
|
+
Y coordinate are deduced from the X coordinate when the string is decoded.
|
96
|
+
|
97
|
+
This code returns a binary string.
|
98
|
+
|
99
|
+
## Decoding a public key from a binary string
|
100
|
+
|
101
|
+
To decode a SEC2 octet string, you can use the code below. The `group` object
|
102
|
+
is assumed to be an `ECDSA::Group`.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
public_key = ECDSA::Format::PointOctetString.decode(public_key_string, group)
|
106
|
+
```
|
107
|
+
|
108
|
+
## Signing a message
|
109
|
+
|
110
|
+
This example shows how to generate a signature for a message. In this example,
|
111
|
+
we will use SHA2 as our digest algorithm, but other algorithms can be used.
|
112
|
+
|
113
|
+
This example assumes that you trust the `SecureRandom` class in your Ruby
|
114
|
+
implementation to generate the temporary key (also known as `k`). Beware that
|
115
|
+
if you accidentally sign two different messages with the same temporary key, it
|
116
|
+
is easy for someone to compute your private key from those two signatures and
|
117
|
+
then forge your signature. Also, if someone can correctly guess the value of
|
118
|
+
the temporary key used for a signature, they can compute your private key from
|
119
|
+
that signature.
|
120
|
+
|
121
|
+
This example assumes that you have required the `ecdsa` gem, that you have an
|
122
|
+
`ECDSA::Group` object named `group`, and that you have the private key stored as
|
123
|
+
an integer in a variable named `private_key`.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
require 'digest/sha2'
|
127
|
+
message = 'ECDSA is cool.'
|
128
|
+
digest = Digest::SHA2.digest(message)
|
129
|
+
signature = nil
|
130
|
+
while signature.nil?
|
131
|
+
temp_key = 1 + SecureRandom.random_number(group.order - 1)
|
132
|
+
signature = ECDSA.sign(group, private_key, digest, temp_key)
|
133
|
+
end
|
134
|
+
puts 'signature: '
|
135
|
+
puts ' r: %#x' % signature.r
|
136
|
+
puts ' s: %#x' % signature.s
|
137
|
+
```
|
138
|
+
|
139
|
+
## Encoding a signature as a DER string
|
140
|
+
|
141
|
+
Signatures can be stored and transmitted as a [DER](http://en.wikipedia.org/wiki/X.690) string.
|
142
|
+
The code below encodes an `ECDSA::Signature` object as a binary DER string.
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
signature_der_string = ECDSA::Format::SignatureDerString.encode(signature)
|
146
|
+
```
|
147
|
+
|
148
|
+
## Decoding a signature from a DER string
|
149
|
+
|
150
|
+
The code below decodes a binary DER string to produce an `ECDSA::Signature` object.
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
signature = ECDSA::Format::SignatureDerString.decode(signature_der_string)
|
154
|
+
```
|
155
|
+
|
156
|
+
## Verifying a signature
|
157
|
+
|
158
|
+
The code below shows how to verify an ECDSA signature. It assumes that you have
|
159
|
+
an `ECDSA::Point` object representing a public key, a string or integer
|
160
|
+
representing the digest of the signed messaged, and an `ECDSA::Signature` object
|
161
|
+
representing the signature. The `valid_signature?` method returns `true` if the
|
162
|
+
signature is valid and `false` if it is not.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
valid = ECDSA.valid_signature?(public_key, digest, signature)
|
166
|
+
puts "valid: #{valid}"
|
167
|
+
```
|
168
|
+
|
169
|
+
## Supported platforms
|
45
170
|
|
46
|
-
|
171
|
+
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.
|
data/lib/ecdsa.rb
CHANGED
@@ -5,7 +5,9 @@ require_relative 'ecdsa/sign'
|
|
5
5
|
require_relative 'ecdsa/format'
|
6
6
|
require_relative 'ecdsa/version'
|
7
7
|
|
8
|
+
# The top-level module for the ECDSA gem.
|
8
9
|
module ECDSA
|
10
|
+
# This method is NOT part of the public API of the ECDSA gem.
|
9
11
|
def self.byte_length(integer)
|
10
12
|
length = 0
|
11
13
|
while integer > 0
|
@@ -15,6 +17,7 @@ module ECDSA
|
|
15
17
|
length
|
16
18
|
end
|
17
19
|
|
20
|
+
# This method is NOT part of the public API of the ECDSA gem.
|
18
21
|
def self.bit_length(integer)
|
19
22
|
length = 0
|
20
23
|
while integer > 0
|
@@ -24,14 +27,16 @@ module ECDSA
|
|
24
27
|
length
|
25
28
|
end
|
26
29
|
|
30
|
+
# This method is NOT part of the public API of the ECDSA gem.
|
27
31
|
def self.convert_digest_to_integer(digest)
|
28
32
|
case digest
|
29
33
|
when Integer then digest
|
30
|
-
when String then
|
34
|
+
when String then Format::IntegerOctetString.decode(digest)
|
31
35
|
else raise "Invalid digest: #{digest.inspect}"
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
39
|
+
# This method is NOT part of the public API of the ECDSA gem.
|
35
40
|
def self.leftmost_bits(n, bit_length)
|
36
41
|
if n >= (1 << (8 * bit_length))
|
37
42
|
raise 'Have not yet written code to handle this case'
|
@@ -40,20 +45,9 @@ module ECDSA
|
|
40
45
|
end
|
41
46
|
end
|
42
47
|
|
48
|
+
# This method is NOT part of the public API of the ECDSA gem.
|
43
49
|
def self.normalize_digest(digest, bit_length)
|
44
50
|
digest_num = convert_digest_to_integer(digest)
|
45
51
|
leftmost_bits(digest_num, bit_length)
|
46
52
|
end
|
47
|
-
|
48
|
-
# SEC1, Section 2.3.2.
|
49
|
-
# My interpretation of that section is that we treat the octet string as BIG endian.
|
50
|
-
def self.convert_octet_string_to_bit_string(string)
|
51
|
-
string.bytes.reduce { |n, b| (n << 8) + b }
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
class String
|
56
|
-
def hex_inspect
|
57
|
-
'"' + each_byte.map { |b| '\x%02x' % b }.join + '"'
|
58
|
-
end
|
59
53
|
end
|
@@ -1,17 +1,29 @@
|
|
1
|
-
# Defined in http://www.secg.org/collateral/sec1_final.pdf
|
2
|
-
# section 2.3.5: FieldElement-to-OctetString Conversion
|
3
|
-
|
4
1
|
module ECDSA
|
5
2
|
module Format
|
3
|
+
# This module provides methods for converting elements of a field to octet
|
4
|
+
# strings. The conversions are defined in these sections of
|
5
|
+
# [SEC1](http://www.secg.org/collateral/sec1_final.pdf):
|
6
|
+
#
|
7
|
+
# * Section 2.3.5: FieldElement-to-OctetString Conversion
|
8
|
+
# * Section 2.3.6: OctetString-to-FieldElement Conversion
|
6
9
|
module FieldElementOctetString
|
7
|
-
#
|
8
|
-
#
|
10
|
+
# Converts a field element to an octet string.
|
11
|
+
#
|
12
|
+
# @param element (Integer) The integer to encode.
|
13
|
+
# @param field (PrimeField)
|
14
|
+
# @return (String)
|
9
15
|
def self.encode(element, field)
|
10
16
|
raise ArgumentError, 'Given element is not an element of the field.' if !field.include?(element)
|
11
17
|
length = ECDSA.byte_length(field.prime)
|
12
18
|
IntegerOctetString.encode(element, length)
|
13
19
|
end
|
14
20
|
|
21
|
+
# Converts an octet string to a field element.
|
22
|
+
# Raises a {DecodeError} if the input string is invalid for any reason.
|
23
|
+
#
|
24
|
+
# @param string (String)
|
25
|
+
# @param field (PrimeField)
|
26
|
+
# @return (Integer)
|
15
27
|
def self.decode(string, field)
|
16
28
|
int = IntegerOctetString.decode(string)
|
17
29
|
|
@@ -1,27 +1,34 @@
|
|
1
|
-
# Defined in http://www.secg.org/collateral/sec1_final.pdf :
|
2
|
-
# Section 2.3.7: Integer-to-OctetString Conversion
|
3
|
-
# Section 2.3.8: OctetString-to-Integer Conversion
|
4
|
-
|
5
1
|
module ECDSA
|
6
2
|
module Format
|
3
|
+
# This module provides methods for converting integers to big endian octet
|
4
|
+
# strings. The conversions are defined in these sections of
|
5
|
+
# [SEC1](http://www.secg.org/collateral/sec1_final.pdf):
|
6
|
+
#
|
7
|
+
# * Section 2.3.7: Integer-to-OctetString Conversion
|
8
|
+
# * Section 2.3.8: OctetString-to-Integer Conversion
|
9
|
+
#
|
10
|
+
# We use Ruby integers to represent bit strings, so this module can also be
|
11
|
+
# thought of as implementing these sections of SEC1:
|
12
|
+
#
|
13
|
+
# * Section 2.3.1: BitString-to-OctetString Conversion
|
14
|
+
# * Section 2.3.2: OctetString-to-BitString Conversion
|
7
15
|
module IntegerOctetString
|
8
|
-
# @param integer (Integer) The integer to encode
|
16
|
+
# @param integer (Integer) The integer to encode.
|
9
17
|
# @param length (Integer) The number of bytes desired in the output string.
|
18
|
+
# @return (String)
|
10
19
|
def self.encode(integer, length)
|
11
20
|
raise ArgumentError, 'Integer to encode is negative.' if integer < 0
|
12
21
|
raise ArgumentError, 'Integer to encode is too large.' if integer >= (1 << (8 * length))
|
13
22
|
|
14
|
-
length.
|
23
|
+
(length - 1).downto(0).map do |i|
|
15
24
|
(integer >> (8 * i)) & 0xFF
|
16
25
|
end.pack('C*')
|
17
26
|
end
|
18
27
|
|
28
|
+
# @param string (String)
|
29
|
+
# @return (Integer)
|
19
30
|
def self.decode(string)
|
20
|
-
|
21
|
-
string.each_byte do |b|
|
22
|
-
integer = (integer << 8) + b.ord
|
23
|
-
end
|
24
|
-
integer
|
31
|
+
string.bytes.reduce { |n, b| (n << 8) + b }
|
25
32
|
end
|
26
33
|
end
|
27
34
|
end
|
@@ -1,14 +1,23 @@
|
|
1
1
|
# encoding: ASCII-8BIT
|
2
2
|
|
3
|
-
# The point octet string format is defined in http://www.secg.org/collateral/sec1_final.pdf .
|
4
|
-
# Section 2.3.3: EllipticCurvePoint-to-OctetString Conversion
|
5
|
-
# Section 2.3.4: OctetString-to-EllipticCurvePoint Conversion
|
6
|
-
|
7
3
|
require_relative '../point'
|
8
4
|
|
9
5
|
module ECDSA
|
10
6
|
module Format
|
7
|
+
# This module provides methods for converting between {Point} objects and "octet strings",
|
8
|
+
# which are just strings with binary data in them that have the coordinates of the point.
|
9
|
+
# The point octet string format is defined in two sections of [SEC1](http://www.secg.org/collateral/sec1_final.pdf):
|
10
|
+
#
|
11
|
+
# * Section 2.3.3: EllipticCurvePoint-to-OctetString Conversion
|
12
|
+
# * Section 2.3.4: OctetString-to-EllipticCurvePoint Conversion
|
11
13
|
module PointOctetString
|
14
|
+
# Converts a {Point} to an octet string.
|
15
|
+
#
|
16
|
+
# @param point (Point)
|
17
|
+
# @param opts (Hash)
|
18
|
+
# @option opts :compression Set this option to true to generate a compressed
|
19
|
+
# octet string, which only has one bit of the Y coordinate.
|
20
|
+
# (Default: false)
|
12
21
|
def self.encode(point, opts = {})
|
13
22
|
return "\x00" if point.infinity?
|
14
23
|
|
@@ -22,9 +31,19 @@ module ECDSA
|
|
22
31
|
end
|
23
32
|
end
|
24
33
|
|
25
|
-
#
|
26
|
-
#
|
34
|
+
# Converts an octet string to a {Point} in the specified group.
|
35
|
+
# Raises a {DecodeError} if the string is invalid for any reason.
|
36
|
+
#
|
37
|
+
# This is equivalent to
|
38
|
+
# [ec_GFp_simple_oct2point](https://github.com/openssl/openssl/blob/a898936/crypto/ec/ecp_oct.c#L325)
|
39
|
+
# in OpenSSL.
|
40
|
+
#
|
41
|
+
# @param string (String)
|
42
|
+
# @param group (Group)
|
43
|
+
# @return (Point)
|
27
44
|
def self.decode(string, group)
|
45
|
+
string = string.dup.force_encoding('BINARY')
|
46
|
+
|
28
47
|
raise DecodeError, 'Point octet string is empty.' if string.empty?
|
29
48
|
|
30
49
|
case string[0].ord
|
@@ -3,7 +3,13 @@ require_relative '../signature'
|
|
3
3
|
|
4
4
|
module ECDSA
|
5
5
|
module Format
|
6
|
+
# This module provides methods to convert between {Signature} objects and their
|
7
|
+
# binary DER representation. The format used is defined in
|
8
|
+
# [RFC 3278 section 8.2](http://tools.ietf.org/html/rfc3278#section-8.2).
|
6
9
|
module SignatureDerString
|
10
|
+
# Converts a DER string to a {Signature}.
|
11
|
+
# @param der_string (String)
|
12
|
+
# @return (Signature)
|
7
13
|
def self.decode(der_string)
|
8
14
|
asn1 = OpenSSL::ASN1.decode(der_string)
|
9
15
|
r = asn1.value[0].value.to_i
|
@@ -11,10 +17,14 @@ module ECDSA
|
|
11
17
|
Signature.new(r, s)
|
12
18
|
end
|
13
19
|
|
20
|
+
# Converts a {Signature} to a DER string.
|
21
|
+
# @param signature (Signature)
|
22
|
+
# @return (String)
|
14
23
|
def self.encode(signature)
|
15
|
-
|
16
|
-
|
17
|
-
|
24
|
+
asn1 = OpenSSL::ASN1::Sequence.new [
|
25
|
+
OpenSSL::ASN1::Integer.new(signature.r),
|
26
|
+
OpenSSL::ASN1::Integer.new(signature.s),
|
27
|
+
]
|
18
28
|
asn1.to_der
|
19
29
|
end
|
20
30
|
end
|
data/lib/ecdsa/group.rb
CHANGED
@@ -3,26 +3,43 @@ require_relative 'point'
|
|
3
3
|
|
4
4
|
module ECDSA
|
5
5
|
class Group
|
6
|
+
# The name of the group.
|
7
|
+
# @return (String)
|
6
8
|
attr_reader :name
|
7
9
|
|
10
|
+
# The generator point.
|
11
|
+
# @return (Point)
|
8
12
|
attr_reader :generator
|
9
13
|
|
14
|
+
# The order of the group. This is the smallest positive
|
15
|
+
# integer `i` such that the generator point multiplied by `i` is infinity.
|
16
|
+
# This is also the number of different points that are on the curve.
|
17
|
+
# @return (Order)
|
10
18
|
attr_reader :order
|
11
19
|
|
20
|
+
# The a parameter in the curve equation (*y^2 = x^3 + ax + b*).
|
21
|
+
# @option opts :a (Integer)
|
12
22
|
attr_reader :param_a
|
13
23
|
|
24
|
+
# The b parameter in the curve equation.
|
25
|
+
# @return (Integer)
|
14
26
|
attr_reader :param_b
|
15
27
|
|
28
|
+
# The field that coordinates on the curve belong to.
|
29
|
+
# @return (PrimeField)
|
16
30
|
attr_reader :field
|
17
31
|
|
18
32
|
# These parameters are defined in http://www.secg.org/collateral/sec2_final.pdf
|
19
33
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
34
|
+
# @param opts (Hash)
|
35
|
+
# @option opts :p (Integer) A prime number that defines the field used. The field will be *F<sub>p</sub>*.
|
36
|
+
# @option opts :a (Integer) The a parameter in the curve equation (*y^2 = x^3 + ax + b*).
|
37
|
+
# @option opts :b (Integer) The b parameter in the curve equation.
|
38
|
+
# @option opts :g (Array(Integer)) The coordinates of the generator point, with x first.
|
39
|
+
# @option opts :n (Integer) The order of g. This is the smallest positive
|
40
|
+
# integer `i` such that the generator point multiplied by `i` is infinity.
|
41
|
+
# This is also the number of different points that are on the curve.
|
42
|
+
# @option opts :h (Integer) The cofactor (optional).
|
26
43
|
def initialize(opts)
|
27
44
|
@opts = opts
|
28
45
|
|
@@ -41,6 +58,9 @@ module ECDSA
|
|
41
58
|
@param_b = field.mod @param_b
|
42
59
|
end
|
43
60
|
|
61
|
+
# Creates a new point.
|
62
|
+
# The argument can either be an array of integers representing the
|
63
|
+
# coordinates, with x first, or it can be `:infinity`.
|
44
64
|
def new_point(p)
|
45
65
|
case p
|
46
66
|
when :infinity
|
@@ -55,48 +75,69 @@ module ECDSA
|
|
55
75
|
end
|
56
76
|
end
|
57
77
|
|
78
|
+
# Returns the infinity point.
|
79
|
+
#
|
80
|
+
# @return (Point)
|
58
81
|
def infinity
|
59
82
|
@infinity ||= Point.new(self, :infinity)
|
60
83
|
end
|
61
84
|
|
62
85
|
# The number of bits that it takes to represent a member of the field.
|
63
86
|
# Log base 2 of the prime p, rounded up.
|
87
|
+
#
|
88
|
+
# @return (Integer)
|
64
89
|
def bit_length
|
65
90
|
@bit_length ||= ECDSA.bit_length(field.prime)
|
66
91
|
end
|
67
92
|
|
93
|
+
# The number of bytes that it takes to represent a member of the field.
|
94
|
+
# Log base 256 of the prime p, rounded up.
|
95
|
+
#
|
96
|
+
# @return (Integer)
|
68
97
|
def byte_length
|
69
98
|
@byte_length ||= ECDSA.byte_length(field.prime)
|
70
99
|
end
|
71
100
|
|
72
|
-
#
|
101
|
+
# Returns true if the point is a solution to the curve's defining equation
|
102
|
+
# or if it is the infinity point.
|
73
103
|
def include?(point)
|
74
|
-
|
104
|
+
return false if point.group != self
|
75
105
|
point.infinity? or point_satisfies_equation?(point)
|
76
106
|
end
|
77
107
|
|
78
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
end
|
82
|
-
|
83
|
-
def equation_right_hand_side(x)
|
84
|
-
field.mod(x * x * x + param_a * x + param_b)
|
85
|
-
end
|
86
|
-
|
108
|
+
# Given the x coordinate of a point, finds all possible corresponding y coordinates.
|
109
|
+
#
|
110
|
+
# @return (Array)
|
87
111
|
def solve_for_y(x)
|
88
112
|
field.square_roots equation_right_hand_side x
|
89
113
|
end
|
90
114
|
|
115
|
+
# @return (String)
|
91
116
|
def inspect
|
92
117
|
"#<#{self.class}:#{name}>"
|
93
118
|
end
|
94
119
|
|
120
|
+
# @return (String)
|
95
121
|
def to_s
|
96
122
|
inspect
|
97
123
|
end
|
98
124
|
|
125
|
+
private
|
126
|
+
|
127
|
+
def point_satisfies_equation?(point)
|
128
|
+
field.square(point.y) == equation_right_hand_side(point.x)
|
129
|
+
end
|
130
|
+
|
131
|
+
def equation_right_hand_side(x)
|
132
|
+
field.mod(x * x * x + param_a * x + param_b)
|
133
|
+
end
|
134
|
+
|
99
135
|
NAMES = %w(
|
136
|
+
Nistp192
|
137
|
+
Nistp224
|
138
|
+
Nistp256
|
139
|
+
Nistp384
|
140
|
+
Nistp521
|
100
141
|
Secp112r1
|
101
142
|
Secp112r2
|
102
143
|
Secp128r1
|
@@ -112,11 +153,6 @@ module ECDSA
|
|
112
153
|
Secp256r1
|
113
154
|
Secp384r1
|
114
155
|
Secp521r1
|
115
|
-
Nistp192
|
116
|
-
Nistp224
|
117
|
-
Nistp256
|
118
|
-
Nistp384
|
119
|
-
Nistp521
|
120
156
|
)
|
121
157
|
|
122
158
|
NAMES.each do |name|
|
data/lib/ecdsa/point.rb
CHANGED
@@ -1,13 +1,31 @@
|
|
1
|
-
# http://www.secg.org/collateral/sec1_final.pdf
|
2
|
-
|
3
1
|
module ECDSA
|
2
|
+
# Instances of this class represent a point on an elliptic curve.
|
3
|
+
# The instances hold their `x` and `y` coordinates along with a reference to
|
4
|
+
# the {Group} (curve) they belong to.
|
5
|
+
#
|
6
|
+
# An instance of this class can also represent the infinity point
|
7
|
+
# (the additive identity of the group), in which case `x` and `y` are `nil`.
|
8
|
+
#
|
9
|
+
# Note: These {Point} objects are not checked when they are created so they
|
10
|
+
# might not actually be on the curve. You can use {Group#include?} to see if
|
11
|
+
# they are on the curve.
|
4
12
|
class Point
|
13
|
+
# @return {Group} the curve that the point is on.
|
5
14
|
attr_reader :group
|
6
15
|
|
16
|
+
# @return (Integer or nil) the x coordinate, or nil for the infinity point.
|
7
17
|
attr_reader :x
|
8
18
|
|
19
|
+
# @return (Integer or nil) the y coordinate, or nil for the infinity point.
|
9
20
|
attr_reader :y
|
10
21
|
|
22
|
+
# Creates a new instance of {Point}.
|
23
|
+
# This method is NOT part of the public interface of the ECDSA gem.
|
24
|
+
# You should call {Group#new_point} instead of calling this directly.
|
25
|
+
#
|
26
|
+
# @param group (Group)
|
27
|
+
# @param args Either the x and y coordinates as integers, or just
|
28
|
+
# `:infinity` to create the infinity point.
|
11
29
|
def initialize(group, *args)
|
12
30
|
@group = group
|
13
31
|
|
@@ -24,43 +42,62 @@ module ECDSA
|
|
24
42
|
end
|
25
43
|
end
|
26
44
|
|
45
|
+
# Returns an array of the coordinates, with `x` first and `y` second.
|
46
|
+
#
|
47
|
+
# @return (Array)
|
27
48
|
def coords
|
28
49
|
[x, y]
|
29
50
|
end
|
30
51
|
|
31
|
-
|
32
|
-
|
52
|
+
# Adds this point to another point on the same curve using the standard
|
53
|
+
# rules for point addition defined in section 2.2.1 of
|
54
|
+
# [SEC1](http://www.secg.org/collateral/sec1_final.pdf).
|
55
|
+
#
|
56
|
+
# @param other (Point)
|
57
|
+
# @return The sum of the two points.
|
58
|
+
def add_to_point(other)
|
59
|
+
check_group! other
|
33
60
|
|
34
61
|
# Assertions:
|
35
62
|
# raise "point given (#{point.inspect}) does not belong to #{group.name}" if !group.include?(point)
|
36
63
|
# raise "point (#{inspect}) does not belong to #{group.name}" if !group.include?(self)
|
37
64
|
|
38
|
-
#
|
39
|
-
return
|
40
|
-
return self if
|
65
|
+
# Rules 1 and 2
|
66
|
+
return other if infinity?
|
67
|
+
return self if other.infinity?
|
41
68
|
|
42
|
-
#
|
43
|
-
return group.infinity if x ==
|
69
|
+
# Rule 3
|
70
|
+
return group.infinity if x == other.x && y == field.mod(-other.y)
|
44
71
|
|
45
|
-
#
|
46
|
-
if x !=
|
47
|
-
gamma = field.mod((
|
48
|
-
sum_x = field.mod(gamma * gamma - x -
|
72
|
+
# Rule 4
|
73
|
+
if x != other.x
|
74
|
+
gamma = field.mod((other.y - y) * field.inverse(other.x - x))
|
75
|
+
sum_x = field.mod(gamma * gamma - x - other.x)
|
49
76
|
sum_y = field.mod(gamma * (x - sum_x) - y)
|
50
77
|
return self.class.new(group, sum_x, sum_y)
|
51
78
|
end
|
52
79
|
|
53
|
-
#
|
54
|
-
return double if self ==
|
80
|
+
# Rule 5
|
81
|
+
return double if self == other
|
55
82
|
|
56
|
-
raise "Failed to add #{inspect} to #{
|
83
|
+
raise "Failed to add #{inspect} to #{other.inspect}: No addition rules matched."
|
57
84
|
end
|
58
85
|
|
86
|
+
# Returns the additive inverse of the point.
|
87
|
+
#
|
88
|
+
# @return (Point)
|
59
89
|
def negate
|
60
90
|
return self if infinity?
|
61
91
|
self.class.new(group, x, field.mod(-y))
|
62
92
|
end
|
63
93
|
|
94
|
+
# Returns the point added to itself.
|
95
|
+
#
|
96
|
+
# This algorithm is defined in
|
97
|
+
# [SEC1](http://www.secg.org/collateral/sec1_final.pdf), Section 2.2.1,
|
98
|
+
# Rule 5.
|
99
|
+
#
|
100
|
+
# @return (Point)
|
64
101
|
def double
|
65
102
|
return self if infinity?
|
66
103
|
gamma = field.mod((3 * x * x + @group.param_a) * field.inverse(2 * y))
|
@@ -69,6 +106,10 @@ module ECDSA
|
|
69
106
|
self.class.new(group, new_x, new_y)
|
70
107
|
end
|
71
108
|
|
109
|
+
# Returns the point multiplied by a non-negative integer.
|
110
|
+
#
|
111
|
+
# @param i (Integer)
|
112
|
+
# @return (Point)
|
72
113
|
def multiply_by_scalar(i)
|
73
114
|
raise ArgumentError, 'Scalar is not an integer.' if !i.is_a?(Integer)
|
74
115
|
raise ArgumentError, 'Scalar is negative.' if i < 0
|
@@ -82,19 +123,33 @@ module ECDSA
|
|
82
123
|
result
|
83
124
|
end
|
84
125
|
|
126
|
+
# Compares this point to another.
|
127
|
+
#
|
128
|
+
# @return (true or false) true if the points are equal
|
85
129
|
def eql?(other)
|
86
130
|
return false if !other.is_a?(Point) || other.group != group
|
87
131
|
x == other.x && y == other.y
|
88
132
|
end
|
89
133
|
|
134
|
+
# Compares this point to another.
|
135
|
+
#
|
136
|
+
# @return (true or false) true if the points are equal
|
90
137
|
def ==(other)
|
91
138
|
eql?(other)
|
92
139
|
end
|
93
140
|
|
141
|
+
# Returns true if this instance represents the infinity point (the additive
|
142
|
+
# identity of the group).
|
143
|
+
#
|
144
|
+
# @return (true or false)
|
94
145
|
def infinity?
|
95
146
|
@infinity == true
|
96
147
|
end
|
97
148
|
|
149
|
+
# Returns a string showing the value of the point which is useful for
|
150
|
+
# debugging.
|
151
|
+
#
|
152
|
+
# @return (String)
|
98
153
|
def inspect
|
99
154
|
if infinity?
|
100
155
|
'#<%s: %s, infinity>' % [self.class, group.name]
|
data/lib/ecdsa/prime_field.rb
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
module ECDSA
|
2
|
+
# Instances of this class represent a field where the elements are non-negative
|
3
|
+
# integers less than a prime *p*.
|
4
|
+
#
|
5
|
+
# To add two elements of the field:
|
6
|
+
#
|
7
|
+
# ```ruby
|
8
|
+
# sum = field.mod(a + b)
|
9
|
+
# ```
|
10
|
+
#
|
11
|
+
# To multiply two elements of the field:
|
12
|
+
#
|
13
|
+
# ```ruby
|
14
|
+
# product = field.mod(a * b)
|
15
|
+
# ```
|
2
16
|
class PrimeField
|
17
|
+
# @return (Integer) the prime number that the field is based on.
|
3
18
|
attr_reader :prime
|
4
19
|
|
5
20
|
def initialize(prime)
|
@@ -7,23 +22,33 @@ module ECDSA
|
|
7
22
|
@prime = prime
|
8
23
|
end
|
9
24
|
|
25
|
+
# Returns true if the given object is an integer and a member of the field.
|
26
|
+
# @param e (Object)
|
27
|
+
# @return (true or false)
|
10
28
|
def include?(e)
|
11
29
|
e.is_a?(Integer) && e >= 0 && e < prime
|
12
30
|
end
|
13
31
|
|
14
|
-
|
15
|
-
|
32
|
+
# Calculates the remainder of `n` after being divided by the field's prime.
|
33
|
+
# @param n (Integer)
|
34
|
+
# @return (Integer)
|
35
|
+
def mod(n)
|
36
|
+
n % prime
|
16
37
|
end
|
17
38
|
|
18
|
-
#
|
19
|
-
|
20
|
-
|
39
|
+
# Computes the multiplicative inverse of a field element using the
|
40
|
+
# [Extended Euclidean Algorithm](http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm).
|
41
|
+
#
|
42
|
+
# @param n (Integer)
|
43
|
+
# @return (Integer) integer `inv` such that `(inv * num) % prime` is one.
|
44
|
+
def inverse(n)
|
45
|
+
raise ArgumentError, '0 has no multiplicative inverse.' if n.zero?
|
21
46
|
|
22
47
|
# For every i, we make sure that num * s[i] + prime * t[i] = r[i].
|
23
48
|
# Eventually r[i] will equal 1 because gcd(num, prime) is always 1.
|
24
49
|
# At that point, s[i] is the multiplicative inverse of num in the field.
|
25
50
|
|
26
|
-
remainders = [
|
51
|
+
remainders = [n, prime]
|
27
52
|
s = [1, 0]
|
28
53
|
t = [0, 1]
|
29
54
|
arrays = [remainders, s, t]
|
@@ -34,12 +59,16 @@ module ECDSA
|
|
34
59
|
end
|
35
60
|
end
|
36
61
|
|
37
|
-
raise 'Inversion bug: remainder is not
|
62
|
+
raise 'Inversion bug: remainder is not 1.' if remainders[-2] != 1
|
38
63
|
mod s[-2]
|
39
64
|
end
|
40
65
|
|
41
|
-
# Computes n raised to the power m
|
42
|
-
# This algorithm uses the same idea as Point#multiply_by_scalar.
|
66
|
+
# Computes `n` raised to the power `m`.
|
67
|
+
# This algorithm uses the same idea as {Point#multiply_by_scalar}.
|
68
|
+
#
|
69
|
+
# @param n (Integer) the base
|
70
|
+
# @param m (Integer) the exponent
|
71
|
+
# @return (Integer)
|
43
72
|
def power(n, m)
|
44
73
|
result = 1
|
45
74
|
v = n
|
@@ -51,11 +80,19 @@ module ECDSA
|
|
51
80
|
result
|
52
81
|
end
|
53
82
|
|
54
|
-
# Computes n
|
83
|
+
# Computes `n` multiplied by itself.
|
84
|
+
# @param n (Integer)
|
85
|
+
# @return (Integer)
|
55
86
|
def square(n)
|
56
87
|
mod n * n
|
57
88
|
end
|
58
89
|
|
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
|
+
#
|
94
|
+
# @param n (Integer)
|
95
|
+
# @return (Array) A sorted array of numbers whose square is equal to `n`.
|
59
96
|
def square_roots(n)
|
60
97
|
raise ArgumentError, "Not a member of the field: #{n}." if !include?(n)
|
61
98
|
if (prime % 4) == 3
|
@@ -74,8 +111,7 @@ module ECDSA
|
|
74
111
|
def square_roots_for_p_3_mod_4(n)
|
75
112
|
candidate = power n, (prime + 1) / 4
|
76
113
|
return [] if square(candidate) != n
|
77
|
-
|
78
|
-
[candidate, mod(-candidate)].sort
|
114
|
+
[candidate, mod(-candidate)].uniq.sort
|
79
115
|
end
|
80
116
|
end
|
81
117
|
end
|
data/lib/ecdsa/sign.rb
CHANGED
@@ -2,21 +2,34 @@ module ECDSA
|
|
2
2
|
class SZeroError < StandardError
|
3
3
|
end
|
4
4
|
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
5
|
+
# Produces an ECDSA signature.
|
6
|
+
#
|
7
|
+
# This algorithm comes from section 4.1.3 of [SEC1](http://www.secg.org/collateral/sec1_final.pdf).
|
8
|
+
#
|
9
|
+
# @param group (Group) The curve that is being used.
|
10
|
+
# @param private_key (Integer) The private key. (The number of times to add
|
11
|
+
# the generator point to itself to get the public key.)
|
12
|
+
# @param digest (String or Integer)
|
13
|
+
# A digest of the message to be signed, usually generated with a hashing algorithm
|
14
|
+
# like SHA2. The same algorithm must be used when verifying the signature.
|
15
|
+
# @param temporary_key (Integer)
|
16
|
+
# A temporary private key.
|
17
|
+
# This is also known as "k" in some documents.
|
18
|
+
# Warning: Never use the same `temporary_key` value twice for two different messages
|
19
|
+
# or else it will be easy for someone to calculate your private key.
|
20
|
+
# The `temporary_key` should be generated with a secure random number generator.
|
21
|
+
# @return (Signature or nil) Usually this method returns a {Signature}, but
|
22
|
+
# there is a very small chance that the calculated "s" value for the
|
23
|
+
# signature will be 0, in which case the method returns nil. If that happens,
|
24
|
+
# you should generate a new temporary key and try again.
|
25
|
+
def self.sign(group, private_key, digest, temporary_key)
|
10
26
|
# Second part of step 1: Select ephemeral elliptic curve key pair
|
11
|
-
#
|
12
|
-
r_point = group.new_point
|
27
|
+
# temporary_key was already selected for us by the caller
|
28
|
+
r_point = group.new_point temporary_key
|
13
29
|
|
14
|
-
#
|
15
|
-
xr = r_point.x
|
16
|
-
|
17
|
-
# Step 3
|
30
|
+
# Steps 2 and 3
|
18
31
|
point_field = PrimeField.new(group.order)
|
19
|
-
r = point_field.mod(
|
32
|
+
r = point_field.mod(r_point.x)
|
20
33
|
|
21
34
|
# Step 4, calculating the hash, was already performed by the caller.
|
22
35
|
|
@@ -24,11 +37,11 @@ module ECDSA
|
|
24
37
|
e = normalize_digest(digest, group.bit_length)
|
25
38
|
|
26
39
|
# Step 6
|
27
|
-
s = point_field.mod(point_field.inverse(
|
40
|
+
s = point_field.mod(point_field.inverse(temporary_key) * (e + r * private_key))
|
28
41
|
|
29
42
|
if s.zero?
|
30
43
|
# We need to go back to step 1, so the caller should generate another
|
31
|
-
# random number
|
44
|
+
# random number temporary_key and try again.
|
32
45
|
return nil
|
33
46
|
end
|
34
47
|
|
data/lib/ecdsa/signature.rb
CHANGED
@@ -1,14 +1,23 @@
|
|
1
1
|
module ECDSA
|
2
|
+
# Instances of this class represents ECDSA signatures,
|
3
|
+
# which are simply a pair of integers named `r` and `s`.
|
2
4
|
class Signature
|
5
|
+
# @return (Integer)
|
3
6
|
attr_reader :r
|
7
|
+
|
8
|
+
# @return (Integer)
|
4
9
|
attr_reader :s
|
5
10
|
|
11
|
+
# @param r (Integer) the value of r.
|
12
|
+
# @param s (Integer) the value of s.
|
6
13
|
def initialize(r, s)
|
7
14
|
@r, @s = r, s
|
8
15
|
r.is_a?(Integer) or raise ArgumentError, 'r is not an integer.'
|
9
16
|
s.is_a?(Integer) or raise ArgumentError, 's is not an integer.'
|
10
17
|
end
|
11
18
|
|
19
|
+
# Returns an array containing `r` first and `s` second.
|
20
|
+
# @return (Array)
|
12
21
|
def components
|
13
22
|
[r, s]
|
14
23
|
end
|
data/lib/ecdsa/verify.rb
CHANGED
@@ -1,21 +1,38 @@
|
|
1
1
|
module ECDSA
|
2
|
+
# An instance of this class is raised if the signature is invalid.
|
2
3
|
class InvalidSignatureError < StandardError
|
3
4
|
end
|
4
5
|
|
5
|
-
#
|
6
|
+
# Verifies the given {Signature} and returns true if it is valid.
|
7
|
+
#
|
8
|
+
# This algorithm comes from Section 4.1.4 of [SEC1](http://www.secg.org/collateral/sec1_final.pdf).
|
9
|
+
#
|
10
|
+
# @param public_key (Point)
|
11
|
+
# @param digest (String or Integer)
|
12
|
+
# @param signature (Signature)
|
13
|
+
# @return true if the ECDSA signature if valid, returns false otherwise.
|
6
14
|
def self.valid_signature?(public_key, digest, signature)
|
7
15
|
check_signature! public_key, digest, signature
|
8
16
|
rescue InvalidSignatureError
|
9
17
|
false
|
10
18
|
end
|
11
19
|
|
20
|
+
# Verifies the given {Signature} and raises an {InvalidSignatureError} if it
|
21
|
+
# is invalid.
|
22
|
+
#
|
23
|
+
# This algorithm comes from Section 4.1.4 of [SEC1](http://www.secg.org/collateral/sec1_final.pdf).
|
24
|
+
#
|
25
|
+
# @param public_key (Point)
|
26
|
+
# @param digest (String or Integer)
|
27
|
+
# @param signature (Signature)
|
28
|
+
# @return true
|
12
29
|
def self.check_signature!(public_key, digest, signature)
|
13
30
|
group = public_key.group
|
14
31
|
field = group.field
|
15
32
|
|
16
33
|
# Step 1: r and s must be in the field and non-zero
|
17
|
-
raise InvalidSignatureError, 'r
|
18
|
-
raise InvalidSignatureError, 's
|
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)
|
19
36
|
raise InvalidSignatureError, 'r is zero.' if signature.r.zero?
|
20
37
|
raise InvalidSignatureError, 's is zero.' if signature.s.zero?
|
21
38
|
|
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: 0.
|
4
|
+
version: 1.0.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-
|
11
|
+
date: 2014-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email: davidegrayson@gmail.com
|
@@ -73,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
73
73
|
version: '2'
|
74
74
|
requirements: []
|
75
75
|
rubyforge_project:
|
76
|
-
rubygems_version: 2.0.
|
76
|
+
rubygems_version: 2.0.14
|
77
77
|
signing_key:
|
78
78
|
specification_version: 4
|
79
79
|
summary: This gem implements the Ellipctic Curve Digital Signature Algorithm (ECDSA)
|