ecdsa 0.1.5 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/DavidEGrayson/ruby_ecdsa.svg?branch=master)](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)
|