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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 43b972ebb083f20d5ea5875f00a6c235d77f1aec
4
- data.tar.gz: 946c40b1039fc9c8fddfd194f58edc1c213f1239
3
+ metadata.gz: 112522081e19ec7215da474caa4ee348b0ccca48
4
+ data.tar.gz: b7ce849fcd146350259a96830992a5e68a020d88
5
5
  SHA512:
6
- metadata.gz: 9466417bfd9244c36997edc141881472658343ba52d03e62d14b603f15ad13ac612009ceb91666c4359d32c9950b3ebd20d33232eb677b33f207c88a8b6ea5ea
7
- data.tar.gz: e5293e8f992ca92257d81076755f7c96243f5e9bab1f04a92acc91d25aef5e9faada7ba83ddfe27ba0ca76ce27abe87fb5a5f0eae719cc838478a5d8fe48f8ba
6
+ metadata.gz: 4112fc44b63041825d083a5ac7fd510a8b7ba800183c89d137e7feed2656fbeca69177fb7dce11bbc44ece676540ae9970043fedbe38ce5c4dcd4add30187b15
7
+ data.tar.gz: 4d9695b86a32f46719dd99e4420d7b4a4c83aa6ba142dbf830d27a6f5be4af63e7208ff674bb0ceba75cfb55db23c863de2a02e98c3ff2812fa59bcdf2ed6961
data/Gemfile CHANGED
@@ -1,4 +1,12 @@
1
1
  source 'https://rubygems.org/'
2
2
 
3
3
  gem 'rspec'
4
- gem 'rubocop', '0.19.1'
4
+
5
+ gem 'rubocop', '0.19.1'
6
+
7
+ gem 'yard'
8
+ gem 'markdown'
9
+
10
+ platforms :ruby, :rbx do
11
+ gem 'redcarpet'
12
+ end
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) almost entirely in pure Ruby.
4
- It aims to be easier to use and easier to understand than Ruby's [OpenSSL EC support](http://www.ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL/PKey/EC.html).
5
- This gem does use OpenSSL but it only uses it to decode and encode ASN1 strings for ECDSA signatures.
6
- All cryptographic calculations are done in pure Ruby.
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 `ECDSA::Signature`.
9
- These classes operate on Ruby integers and do not deal at all with binary formatting.
10
- Encoding and decoding of binary formats is solely handled by classes under the `ECDSA::Format` module.
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` object or you can
13
- use a pre-existing group object such as `ECDSA::Group::Secp256k1`.
14
- The pre-existing groups can be seen in the `lib/ecdsa/group` folder, and include all the curves
15
- defined in [SEC2](http://www.secg.org/collateral/sec2_final.pdf) and [NIST's Recommended Elliptic Curves for Federal Government Use](http://csrc.nist.gov/groups/ST/toolkit/documents/dss/NISTReCur.pdf).
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_ between 0
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 sign
21
- two different messages, or else it would be easy for someone to compute your
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 (_F<sub>p</sub>_).
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 compressed form.
33
- - There is no documentation. If you know a little bit about ECDSA and know how to read
34
- Ruby source code, you can probably figure it out though.
35
- - The algorithms have not been optimized for speed, and will probably never be, because that
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 checked.
39
- It is provided "as is" and it is the user's responsibility to make sure it will be
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 destributed as a gem named [ecdsa](https://rubygems.org/gems/ecdsa) at RubyGems.org. To install it, run:
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
- gem install ecdsa
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.
@@ -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 convert_octet_string_to_bit_string(digest)
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
- # @param integer (Integer) The integer to encode
8
- # @param length (Integer) The number of bytes desired in the output string.
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.pred.downto(0).map do |i|
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
- integer = 0
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
- # This is equivalent to ec_GFp_simple_oct2point in OpenSSL:
26
- # https://github.com/openssl/openssl/blob/a898936218bc279b5d7cdf76d58a25e7a2d419cb/crypto/ec/ecp_oct.c
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
- ra = OpenSSL::ASN1::Integer.new signature.r
16
- sa = OpenSSL::ASN1::Integer.new signature.s
17
- asn1 = OpenSSL::ASN1::Sequence.new [ra, sa]
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
@@ -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
- # - +p+: A prime number that defines the field used. The field will be F_p.
21
- # - +a+: The a parameter in the curve equation (y^2 = x^3 + ax + b).
22
- # - +b+: The b parameter in the curve equation.
23
- # - +g+: The base point as an octet string.
24
- # - +n+: The order of g.
25
- # - +h+: The cofactor.
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
- # Verify that the point is a solution to the curve's defining equation.
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
- raise 'Group mismatch.' if point.group != self
104
+ return false if point.group != self
75
105
  point.infinity? or point_satisfies_equation?(point)
76
106
  end
77
107
 
78
- # You should probably use include? instead of this.
79
- def point_satisfies_equation?(point)
80
- field.mod(point.y * point.y) == equation_right_hand_side(point.x)
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|
@@ -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
- def add_to_point(point)
32
- check_group! point
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
- # SEC1, section 2.2.1, rules 1 and 2
39
- return point if infinity?
40
- return self if point.infinity?
65
+ # Rules 1 and 2
66
+ return other if infinity?
67
+ return self if other.infinity?
41
68
 
42
- # SEC1, section 2.2.1, rule 3
43
- return group.infinity if x == point.x && y == field.mod(-point.y)
69
+ # Rule 3
70
+ return group.infinity if x == other.x && y == field.mod(-other.y)
44
71
 
45
- # SEC1, section 2.2.1, rule 4
46
- if x != point.x
47
- gamma = field.mod((point.y - y) * field.inverse(point.x - x))
48
- sum_x = field.mod(gamma * gamma - x - point.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
- # SEC2, section 2.2.1, rule 5
54
- return double if self == point
80
+ # Rule 5
81
+ return double if self == other
55
82
 
56
- raise "Failed to add #{inspect} to #{point.inspect}: No addition rules matched."
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]
@@ -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
- def mod(num)
15
- num % prime
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
- # http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
19
- def inverse(num)
20
- raise ArgumentError, '0 has no multiplicative inverse.' if num.zero?
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 = [num, prime]
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 than 1.' if remainders[-2] != 1
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^2.
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
- return [candidate] if candidate.zero?
78
- [candidate, mod(-candidate)].sort
114
+ [candidate, mod(-candidate)].uniq.sort
79
115
  end
80
116
  end
81
117
  end
@@ -2,21 +2,34 @@ module ECDSA
2
2
  class SZeroError < StandardError
3
3
  end
4
4
 
5
- # From http://www.secg.org/collateral/sec1_final.pdf section 4.1.3
6
- # Warning: Never use the same k value twice for two different messages
7
- # or else it will be trivial for someone to calculate your private key.
8
- # k should be generated with a secure random number generator.
9
- def self.sign(group, private_key, digest, k)
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
- # k was already selected for us by the caller
12
- r_point = group.new_point k
27
+ # temporary_key was already selected for us by the caller
28
+ r_point = group.new_point temporary_key
13
29
 
14
- # Step 2
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(xr)
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(k) * (e + r * private_key))
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 k and try again.
44
+ # random number temporary_key and try again.
32
45
  return nil
33
46
  end
34
47
 
@@ -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
@@ -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
- # Algorithm taken from http://www.secg.org/collateral/sec1_final.pdf Section 4.1.4.
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 value is not the field.' if !field.include?(signature.r)
18
- raise InvalidSignatureError, 's value is not the field.' if !field.include?(signature.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
 
@@ -1,3 +1,3 @@
1
1
  module ECDSA
2
- VERSION = '0.1.5'
2
+ VERSION = '1.0.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecdsa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
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-03-18 00:00:00.000000000 Z
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.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)