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 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)