ecdsa 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d725d0e513f16f2ec4b24170b5d2f85c26472d79
4
+ data.tar.gz: 866b36f01ad0f3e0cf3f32b2ec01881252e564b7
5
+ SHA512:
6
+ metadata.gz: d00fa2b23d9fc9225b6bbe7ae9e1b84254b3e31a5163974179f3ce8bbd3cde4c7d48012de2ba54a238d76cc068f9d0f0310eff985ebdb296191036fbba9f6c07
7
+ data.tar.gz: 84610f1b18e5e8e3b633e932fc58eb950d37a4b193a87ea49e9a16bfd454a1c961278276f0bad1c49d26c7d19cf8c4f3ee4b3690e29cc13ca1c9a79b3ba5c070
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org/'
2
+
3
+ gem 'rspec'
4
+ gem 'rubocop', '0.19.1'
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 David Grayson.
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ # ECDSA gem for Ruby
2
+
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.
7
+
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.
11
+
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).
16
+
17
+ This gem is hosted at the [DavidEGrayson/ruby_ecdsa github repository](https://github.com/DavidEGrayson/ruby_ecdsa).
18
+
19
+ ## Current limitations
20
+
21
+ - This gem only supports fields of integers modulo a prime number (_F<sub>p</sub>_).
22
+ ECDSA's characteristic 2 fields are not supported.
23
+ - This gem can only compute square roots in prime fields over a prime _p_
24
+ that is one less than a multiple of 4.
25
+ Computing a square root is required for parsing public keys stored in compressed form.
26
+ - There is no documentation. If you know ECDSA and know how to read Ruby source code,
27
+ you can probably figure it out though.
28
+ - The algorithms have not been optimized for speed, and will probably never be, because that
29
+ would hinder the goal of helping people understand ECDSA.
@@ -0,0 +1,59 @@
1
+ require_relative 'ecdsa/group'
2
+ require_relative 'ecdsa/signature'
3
+ require_relative 'ecdsa/verify'
4
+ require_relative 'ecdsa/sign'
5
+ require_relative 'ecdsa/format'
6
+ require_relative 'ecdsa/version'
7
+
8
+ module ECDSA
9
+ def self.byte_length(integer)
10
+ length = 0
11
+ while integer > 0
12
+ length += 1
13
+ integer >>= 8
14
+ end
15
+ length
16
+ end
17
+
18
+ def self.bit_length(integer)
19
+ length = 0
20
+ while integer > 0
21
+ length += 1
22
+ integer >>= 1
23
+ end
24
+ length
25
+ end
26
+
27
+ def self.convert_digest_to_integer(digest)
28
+ case digest
29
+ when Integer then digest
30
+ when String then convert_octet_string_to_bit_string(digest)
31
+ else raise "Invalid digest: #{digest.inspect}"
32
+ end
33
+ end
34
+
35
+ def self.leftmost_bits(n, bit_length)
36
+ if n >= (1 << (8 * bit_length))
37
+ raise 'Have not yet written code to handle this case'
38
+ else
39
+ n
40
+ end
41
+ end
42
+
43
+ def self.normalize_digest(digest, bit_length)
44
+ digest_num = convert_digest_to_integer(digest)
45
+ leftmost_bits(digest_num, bit_length)
46
+ 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
+ end
@@ -0,0 +1,5 @@
1
+ require 'ecdsa/format/decode_error'
2
+ require 'ecdsa/format/point_octet_string'
3
+ require 'ecdsa/format/integer_octet_string'
4
+ require 'ecdsa/format/field_element_octet_string'
5
+ require 'ecdsa/format/signature_der_string'
@@ -0,0 +1,11 @@
1
+ module ECDSA
2
+ module Format
3
+ # Raising instance of this class as an exception indicates that
4
+ # the data being decoded was invalid, but does not necessarily
5
+ # indicate a bug in the program, unlike most other exceptions
6
+ # because it is possible the data being decoded is coming from
7
+ # an untrusted source.
8
+ class DecodeError < StandardError
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ # Defined in http://www.secg.org/collateral/sec1_final.pdf
2
+ # section 2.3.5: FieldElement-to-OctetString Conversion
3
+
4
+ module ECDSA
5
+ module Format
6
+ module FieldElementOctetString
7
+ # @param integer (Integer) The integer to encode
8
+ # @param length (Integer) The number of bytes desired in the output string.
9
+ def self.encode(element, field)
10
+ raise ArgumentError, 'Given element is not an element of the field.' if !field.include?(element)
11
+ length = ECDSA.byte_length(field.prime)
12
+ IntegerOctetString.encode(element, length)
13
+ end
14
+
15
+ def self.decode(string, field)
16
+ int = IntegerOctetString.decode(string)
17
+
18
+ if !field.include?(int)
19
+ # The integer has to be non-negative, so it must be too big.
20
+ raise DecodeError, 'Decoded integer is too large for field: 0x%x >= 0x%x.' % [int, field.prime]
21
+ end
22
+
23
+ int
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
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
+ module ECDSA
6
+ module Format
7
+ module IntegerOctetString
8
+ # @param integer (Integer) The integer to encode
9
+ # @param length (Integer) The number of bytes desired in the output string.
10
+ def self.encode(integer, length)
11
+ raise ArgumentError, 'Integer to encode is negative.' if integer < 0
12
+ raise ArgumentError, 'Integer to encode is too large.' if integer >= (1 << (8 * length))
13
+
14
+ length.pred.downto(0).map do |i|
15
+ integer >> (8 * i)
16
+ end.pack('C*')
17
+ end
18
+
19
+ def self.decode(string)
20
+ integer = 0
21
+ string.each_byte do |b|
22
+ integer = (integer << 8) + b.ord
23
+ end
24
+ integer
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,87 @@
1
+ # encoding: US-ASCII
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
+ require_relative '../point'
8
+
9
+ module ECDSA
10
+ module Format
11
+ module PointOctetString
12
+ def self.encode(point, opts = {})
13
+ return "\x00" if point.infinity?
14
+
15
+ if opts[:compression]
16
+ start_byte = point.y.even? ? "\x02" : "\x03"
17
+ start_byte + FieldElementOctetString.encode(point.x, point.group.field)
18
+ else
19
+ "\x04" +
20
+ FieldElementOctetString.encode(point.x, point.group.field) +
21
+ FieldElementOctetString.encode(point.y, point.group.field)
22
+ end
23
+ end
24
+
25
+ # This is equivalent to ec_GFp_simple_oct2point in OpenSSL:
26
+ # https://github.com/openssl/openssl/blob/a898936218bc279b5d7cdf76d58a25e7a2d419cb/crypto/ec/ecp_oct.c
27
+ def self.decode(string, group)
28
+ raise DecodeError, 'Point octet string is empty.' if string.empty?
29
+
30
+ case string[0].ord
31
+ when 0
32
+ check_length string, 1
33
+ return group.infinity_point
34
+ when 2
35
+ decode_compressed string, group, 0
36
+ when 3
37
+ decode_compressed string, group, 1
38
+ when 4
39
+ decode_uncompressed string, group
40
+ else
41
+ raise DecodeError, 'Unrecognized start byte for point octet string: 0x%x' % string[0].ord
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def self.decode_compressed(string, group, y_lsb)
48
+ expected_length = 1 + group.byte_length
49
+ check_length string, expected_length
50
+
51
+ x_string = string[1, group.byte_length]
52
+ x = ECDSA::Format::FieldElementOctetString.decode x_string, group.field
53
+
54
+ possible_ys = group.solve_for_y(x)
55
+ y = possible_ys.find { |py| (py % 2) == y_lsb }
56
+
57
+ finish_decode x, y, group
58
+ end
59
+
60
+ def self.decode_uncompressed(string, group)
61
+ expected_length = 1 + 2 * group.byte_length
62
+ check_length string, expected_length
63
+
64
+ x_string = string[1, group.byte_length]
65
+ y_string = string[1 + group.byte_length, group.byte_length]
66
+ x = ECDSA::Format::FieldElementOctetString.decode x_string, group.field
67
+ y = ECDSA::Format::FieldElementOctetString.decode y_string, group.field
68
+
69
+ finish_decode x, y, group
70
+ end
71
+
72
+ def self.finish_decode(x, y, group)
73
+ point = group.new_point [x, y]
74
+ if !group.include? point
75
+ raise DecodeError, "Decoded point does not satisfy curve equation: #{point.inspect}."
76
+ end
77
+ point
78
+ end
79
+
80
+ def self.check_length(string, expected_length)
81
+ if string.length != expected_length
82
+ raise DecodeError, "Expected point octet string to be length #{expected_length} but it was #{string.length}."
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,22 @@
1
+ require 'openssl'
2
+ require_relative '../signature'
3
+
4
+ module ECDSA
5
+ module Format
6
+ module SignatureDerString
7
+ def self.decode(der_string)
8
+ asn1 = OpenSSL::ASN1.decode(der_string)
9
+ r = asn1.value[0].value.to_i
10
+ s = asn1.value[1].value.to_i
11
+ Signature.new(r, s)
12
+ end
13
+
14
+ 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]
18
+ asn1.to_der
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,126 @@
1
+ require_relative 'prime_field'
2
+ require_relative 'point'
3
+
4
+ module ECDSA
5
+ class Group
6
+ attr_reader :name
7
+
8
+ attr_reader :generator
9
+
10
+ attr_reader :order
11
+
12
+ attr_reader :param_a
13
+
14
+ attr_reader :param_b
15
+
16
+ attr_reader :field
17
+
18
+ # These parameters are defined in http://www.secg.org/collateral/sec2_final.pdf
19
+ #
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.
26
+ def initialize(opts)
27
+ @opts = opts
28
+
29
+ @name = opts.fetch(:name) { '%#x' % object_id }
30
+ @field = PrimeField.new(opts[:p])
31
+ @param_a = opts[:a]
32
+ @param_b = opts[:b]
33
+ @generator = new_point(@opts[:g])
34
+ @order = opts[:n]
35
+ @cofactor = opts[:h]
36
+
37
+ @param_a.is_a?(Integer) or raise ArgumentError, 'Invalid a.'
38
+ @param_b.is_a?(Integer) or raise ArgumentError, 'Invalid b.'
39
+
40
+ @param_a = field.mod @param_a
41
+ @param_b = field.mod @param_b
42
+ end
43
+
44
+ def new_point(p)
45
+ case p
46
+ when :infinity
47
+ infinity_point
48
+ when Array
49
+ x, y = p
50
+ Point.new(self, x, y)
51
+ when Integer
52
+ generator.multiply_by_scalar(p)
53
+ else
54
+ raise ArgumentError, "Invalid point specifier #{p.inspect}."
55
+ end
56
+ end
57
+
58
+ def infinity_point
59
+ @infinity_point ||= Point.new(self, :infinity)
60
+ end
61
+
62
+ # The number of bits that it takes to represent a member of the field.
63
+ # Log base 2 of the prime p, rounded up.
64
+ def bit_length
65
+ @bit_length ||= ECDSA.bit_length(field.prime)
66
+ end
67
+
68
+ def byte_length
69
+ @byte_length ||= ECDSA.byte_length(field.prime)
70
+ end
71
+
72
+ # Verify that the point is a solution to the curve's defining equation.
73
+ def include?(point)
74
+ raise 'Group mismatch.' if point.group != self
75
+ point.infinity? or point_satisfies_equation?(point)
76
+ end
77
+
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
+
87
+ def solve_for_y(x)
88
+ field.square_roots equation_right_hand_side x
89
+ end
90
+
91
+ def inspect
92
+ "#<#{self.class}:#{name}>"
93
+ end
94
+
95
+ def to_s
96
+ inspect
97
+ end
98
+
99
+ NAMES = %w(
100
+ Secp112r1
101
+ Secp112r2
102
+ Secp128r1
103
+ Secp128r2
104
+ Secp160k1
105
+ Secp160r1
106
+ Secp160r2
107
+ Secp192k1
108
+ Secp192r1
109
+ Secp224k1
110
+ Secp224r1
111
+ Secp256k1
112
+ Secp256r1
113
+ Secp384r1
114
+ Secp521r1
115
+ Nistp192
116
+ Nistp224
117
+ Nistp256
118
+ Nistp384
119
+ Nistp521
120
+ )
121
+
122
+ NAMES.each do |name|
123
+ autoload name, 'ecdsa/group/' + name.downcase
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://csrc.nist.gov/groups/ST/toolkit/documents/dss/NISTReCur.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Nistp192 = new(
6
+ name: 'nistp192',
7
+ p: 62771017353866807638357894232076664160839087_00390324961279,
8
+ a: -3,
9
+ b: 0x64210519_e59c80e7_0fa7e9ab_72243049_feb8deec_c146b9b1,
10
+ g: [0x188da80e_b03090f6_7cbf20eb_43a18800_f4ff0afd_82ff1012,
11
+ 0x07192b95_ffc8da78_631011ed_6b24cdd5_73f977a1_1e794811],
12
+ n: 62771017353866807638357894231760590137671947_73182842284081,
13
+ h: nil, # cofactor not given in NIST document
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://csrc.nist.gov/groups/ST/toolkit/documents/dss/NISTReCur.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Nistp224 = new(
6
+ name: 'nistp224',
7
+ p: 26959946667150639794667015087019630673557916_260026308143510066298881,
8
+ a: -3,
9
+ b: 0xb4050a85_0c04b3ab_f5413256_5044b0b7_d7bfd8ba_270b3943_2355ffb4,
10
+ g: [0xb70e0cbd_6bb4bf7f_321390b9_4a03c1d3_56c21122_343280d6_115c1d21,
11
+ 0xbd376388_b5f723fb_4c22dfe6_cd4375a0_5a074764_44d58199_85007e34],
12
+ n: 26959946667150639794667015087019625940457807_714424391721682722368061,
13
+ h: nil, # cofactor not given in NIST document
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://csrc.nist.gov/groups/ST/toolkit/documents/dss/NISTReCur.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Nistp256 = new(
6
+ name: 'nistp256',
7
+ p: 11579208921035624876269744694940757353008614_3415290314195533631308867097853951,
8
+ a: -3,
9
+ b: 0x5ac635d8_aa3a93e7_b3ebbd55_769886bc_651d06b0_cc53b0f6_3bce3c3e_27d2604b,
10
+ g: [0x6b17d1f2_e12c4247_f8bce6e5_63a440f2_77037d81_2deb33a0_f4a13945_d898c296,
11
+ 0x4fe342e2_fe1a7f9b_8ee7eb4a_7c0f9e16_2bce3357_6b315ece_cbb64068_37bf51f5],
12
+ n: 11579208921035624876269744694940757352999695_5224135760342422259061068512044369,
13
+ h: nil, # cofactor not given in NIST document
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # Source: http://csrc.nist.gov/groups/ST/toolkit/documents/dss/NISTReCur.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Nistp384 = new(
6
+ name: 'nistp384',
7
+ p: 39402006196394479212279040100143613805079739_27046544666794829340424572177149687032904726_6088258938001861606973112319,
8
+ a: -3,
9
+ b: 0xb3312fa7_e23ee7e4_988e056b_e3f82d19_181d9c6e_fe814112_0314088f_5013875a_c656398d_8a2ed19d_2a85c8ed_d3ec2aef,
10
+ g: [0xaa87ca22_be8b0537_8eb1c71e_f320ad74_6e1d3b62_8ba79b98_59f741e0_82542a38_5502f25d_bf55296c_3a545e38_72760ab7,
11
+ 0x3617de4a_96262c6f_5d9e98bf_9292dc29_f8f41dbd_289a147c_e9da3113_b5f0b8c0_0a60b1ce_1d7e819d_7a431d7c_90ea0e5f,
12
+ ],
13
+ n: 39402006196394479212279040100143613805079739_27046544666794690527962765939911326356939895_6308152294913554433653942643,
14
+ h: nil, # cofactor not given in NIST document
15
+ )
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # Source: "Curve P-256" from http://csrc.nist.gov/groups/ST/toolkit/documents/dss/NISTReCur.doc
2
+
3
+ module ECDSA
4
+ class Group
5
+ Nistp521 = new(
6
+ name: 'nistp521',
7
+ p: 68647976601306097149819007990813932172694353_00143305409394463459185543183397656052122559_64066145455497729631139148085803712198799971_6643812574028291115057151,
8
+ a: -3,
9
+ b: 0x051_953eb961_8e1c9a1f_929a21a0_b68540ee_a2da725b_99b315f3_b8b48991_8ef109e1_56193951_ec7e937b_1652c0bd_3bb1bf07_3573df88_3d2c34f1_ef451fd4_6b503f00,
10
+ g: [0x00c6_858e06b7_0404e9cd_9e3ecb66_2395b442_9c648139_053fb521_f828af60_6b4d3dba_a14b5e77_efe75928_fe1dc127_a2ffa8de_3348b3c1_856a429b_f97e7e31_c2e5bd66,
11
+ 0x0118_39296a78_9a3bc004_5c8a5fb4_2c7d1bd9_98f54449_579b4468_17afbd17_273e662c_97ee7299_5ef42640_c550b901_3fad0761_353c7086_a272c240_88be9476_9fd16650],
12
+ n: 68647976601306097149819007990813932172694353_00143305409394463459185543183397655394245057_74633321719753296399637136332111386476861244_0380340372808892707005449,
13
+ h: nil, # cofactor not given in NIST document
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp112r1 = new(
6
+ name: 'secp112r1',
7
+ p: 0xDB7C_2ABF62E3_5E668076_BEAD208B,
8
+ a: 0xDB7C_2ABF62E3_5E668076_BEAD2088,
9
+ b: 0x659E_F8BA0439_16EEDE89_11702B22,
10
+ g: [0x0948_7239995A_5EE76B55_F9C2F098,
11
+ 0xA89C_E5AF8724_C0A23E0E_0FF77500],
12
+ n: 0xDB7C_2ABF62E3_5E7628DF_AC6561C5,
13
+ h: 1,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp112r2 = new(
6
+ name: 'secp112r2',
7
+ p: 0xDB7C_2ABF62E3_5E668076_BEAD208B,
8
+ a: 0x6127_C24C05F3_8A0AAAF6_5C0EF02C,
9
+ b: 0x51DE_F1815DB5_ED74FCC3_4C85D709,
10
+ g: [0x4BA3_0AB5E892_B4E1649D_D0928643,
11
+ 0xADCD_46F5882E_3747DEF3_6E956E97],
12
+ n: 0x36DF_0AAFD8B8_D7597CA1_0520D04B,
13
+ h: 4,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp128r1 = new(
6
+ name: 'secp128r1',
7
+ p: 0xFFFFFFFD_FFFFFFFF_FFFFFFFF_FFFFFFFF,
8
+ a: 0xFFFFFFFD_FFFFFFFF_FFFFFFFF_FFFFFFFC,
9
+ b: 0xE87579C1_1079F43D_D824993C_2CEE5ED3,
10
+ g: [0x161FF752_8B899B2D_0C28607C_A52C5B86,
11
+ 0xCF5AC839_5BAFEB13_C02DA292_DDED7A83],
12
+ n: 0xFFFFFFFE_00000000_75A30D1B_9038A115,
13
+ h: 1,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp128r2 = new(
6
+ name: 'secp128r2',
7
+ p: 0xFFFFFFFD_FFFFFFFF_FFFFFFFF_FFFFFFFF,
8
+ a: 0xD6031998_D1B3BBFE_BF59CC9B_BFF9AEE1,
9
+ b: 0x5EEEFCA3_80D02919_DC2C6558_BB6D8A5D,
10
+ g: [0x7B6AA5D8_5E572983_E6FB32A7_CDEBC140,
11
+ 0x27B6916A_894D3AEE_7106FE80_5FC34B44],
12
+ n: 0x3FFFFFFF_7FFFFFFF_BE002472_0613B5A3,
13
+ h: 4,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp160k1 = new(
6
+ name: 'secp160k1',
7
+ p: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFAC73,
8
+ a: 0,
9
+ b: 7,
10
+ g: [0x3B4C382C_E37AA192_A4019E76_3036F4F5_DD4D7EBB,
11
+ 0x938CF935_318FDCED_6BC28286_531733C3_F03C4FEE],
12
+ n: 0x01_00000000_00000000_0001B8FA_16DFAB9A_CA16B6B3,
13
+ h: 1,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp160r1 = new(
6
+ name: 'secp160r1',
7
+ p: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_7FFFFFFF,
8
+ a: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_7FFFFFFC,
9
+ b: 0x1C97BEFC_54BD7A8B_65ACF89F_81D4D4AD_C565FA45,
10
+ g: [0x4A96B568_8EF57328_46646989_68C38BB9_13CBFC82,
11
+ 0x23A62855_3168947D_59DCC912_04235137_7AC5FB32],
12
+ n: 0x01_00000000_00000000_0001F4C8_F927AED3_CA752257,
13
+ h: 1,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp160r2 = new(
6
+ name: 'secp160r2',
7
+ p: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFAC73,
8
+ a: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFAC70,
9
+ b: 0xB4E134D3_FB59EB8B_AB572749_04664D5A_F50388BA,
10
+ g: [0x52DCB034_293A117E_1F4FF11B_30F7199D_3144CE6D,
11
+ 0xFEAFFEF2_E331F296_E071FA0D_F9982CFE_A7D43F2E],
12
+ n: 0x01_00000000_00000000_0000351E_E786A818_F3A1A16B,
13
+ h: 1,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp192k1 = new(
6
+ name: 'secp192k1',
7
+ p: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFEE37,
8
+ a: 0,
9
+ b: 3,
10
+ g: [0xDB4FF10E_C057E9AE_26B07D02_80B7F434_1DA5D1B1_EAE06C7D,
11
+ 0x9B2F2F6D_9C5628A7_844163D0_15BE8634_4082AA88_D95E2F9D],
12
+ n: 0xFFFFFFFF_FFFFFFFF_FFFFFFFE_26F2FC17_0F69466A_74DEFD8D,
13
+ h: 1,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp192r1 = new(
6
+ name: 'secp192r1',
7
+ p: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFFFFF_FFFFFFFF,
8
+ a: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFFFFF_FFFFFFFC,
9
+ b: 0x64210519_E59C80E7_0FA7E9AB_72243049_FEB8DEEC_C146B9B1,
10
+ g: [0x188DA80E_B03090F6_7CBF20EB_43A18800_F4FF0AFD_82FF1012,
11
+ 0x07192B95_FFC8DA78_631011ED_6B24CDD5_73F977A1_1E794811],
12
+ n: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_99DEF836_146BC9B1_B4D22831,
13
+ h: 1,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp224k1 = new(
6
+ name: 'secp224k1',
7
+ p: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFE56D,
8
+ a: 0,
9
+ b: 5,
10
+ g: [0xA1455B33_4DF099DF_30FC28A1_69A467E9_E47075A9_0F7E650E_B6B7A45C,
11
+ 0x7E089FED_7FBA3442_82CAFBD6_F7E319F7_C0B0BD59_E2CA4BDB_556D61A5],
12
+ n: 0x01_00000000_00000000_00000000_0001DCE8_D2EC6184_CAF0A971_769FB1F7,
13
+ h: 1,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp224r1 = new(
6
+ name: 'secp224r1',
7
+ p: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_00000000_00000000_00000001,
8
+ a: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFFFFF_FFFFFFFF_FFFFFFFE,
9
+ b: 0xB4050A85_0C04B3AB_F5413256_5044B0B7_D7BFD8BA_270B3943_2355FFB4,
10
+ g: [0xB70E0CBD_6BB4BF7F_321390B9_4A03C1D3_56C21122_343280D6_115C1D21,
11
+ 0xBD376388_B5F723FB_4C22DFE6_CD4375A0_5A074764_44D58199_85007E34],
12
+ n: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFF16A2_E0B8F03E_13DD2945_5C5C2A3D,
13
+ h: 1,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp256k1 = new(
6
+ name: 'secp256k1',
7
+ p: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFFC2F,
8
+ a: 0,
9
+ b: 7,
10
+ g: [0x79BE667E_F9DCBBAC_55A06295_CE870B07_029BFCDB_2DCE28D9_59F2815B_16F81798,
11
+ 0x483ADA77_26A3C465_5DA4FBFC_0E1108A8_FD17B448_A6855419_9C47D08F_FB10D4B8],
12
+ n: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141,
13
+ h: 1,
14
+ )
15
+
16
+ # compressed G: "\x02\x79\xBE\x66\x7E\xF9\xDC\xBB\xAC\x55\xA0\x62\x95\xCE\x87\x0B\x07\x02\x9B\xFC\xDB\x2D\xCE\x28\xD9\x59\xF2\x81\x5B\x16\xF8\x17\x98",
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp256r1 = new(
6
+ name: 'secp256r1',
7
+ p: 0xFFFFFFFF_00000001_00000000_00000000_00000000_FFFFFFFF_FFFFFFFF_FFFFFFFF,
8
+ a: 0xFFFFFFFF_00000001_00000000_00000000_00000000_FFFFFFFF_FFFFFFFF_FFFFFFFC,
9
+ b: 0x5AC635D8_AA3A93E7_B3EBBD55_769886BC_651D06B0_CC53B0F6_3BCE3C3E_27D2604B,
10
+ g: [0x6B17D1F2_E12C4247_F8BCE6E5_63A440F2_77037D81_2DEB33A0_F4A13945_D898C296,
11
+ 0x4FE342E2_FE1A7F9B_8EE7EB4A_7C0F9E16_2BCE3357_6B315ECE_CBB64068_37BF51F5],
12
+ n: 0xFFFFFFFF_00000000_FFFFFFFF_FFFFFFFF_BCE6FAAD_A7179E84_F3B9CAC2_FC632551,
13
+ h: 1,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp384r1 = new(
6
+ name: 'secp384r1',
7
+ p: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFFFFF_00000000_00000000_FFFFFFFF,
8
+ a: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFFFFF_00000000_00000000_FFFFFFFC,
9
+ b: 0xB3312FA7_E23EE7E4_988E056B_E3F82D19_181D9C6E_FE814112_0314088F_5013875A_C656398D_8A2ED19D_2A85C8ED_D3EC2AEF,
10
+ g: [0xAA87CA22_BE8B0537_8EB1C71E_F320AD74_6E1D3B62_8BA79B98_59F741E0_82542A38_5502F25D_BF55296C_3A545E38_72760AB7,
11
+ 0x3617DE4A_96262C6F_5D9E98BF_9292DC29_F8F41DBD_289A147C_E9DA3113_B5F0B8C0_0A60B1CE_1D7E819D_7A431D7C_90EA0E5F],
12
+ n: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_C7634D81_F4372DDF_581A0DB2_48B0A77A_ECEC196A_CCC52973,
13
+ h: 1,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # Source: http://www.secg.org/collateral/sec2_final.pdf
2
+
3
+ module ECDSA
4
+ class Group
5
+ Secp521r1 = new(
6
+ name: 'secp521r1',
7
+ p: 0x01FF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF,
8
+ a: 0x01FF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFC,
9
+ b: 0x0051_953EB961_8E1C9A1F_929A21A0_B68540EE_A2DA725B_99B315F3_B8B48991_8EF109E1_56193951_EC7E937B_1652C0BD_3BB1BF07_3573DF88_3D2C34F1_EF451FD4_6B503F00,
10
+ g: [0x00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66,
11
+ 0x011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650],
12
+ n: 0x01FF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFA_51868783_BF2F966B_7FCC0148_F709A5D0_3BB5C9B8_899C47AE_BB6FB71E_91386409,
13
+ h: 1,
14
+ )
15
+ end
16
+ end
@@ -0,0 +1,113 @@
1
+ # http://www.secg.org/collateral/sec1_final.pdf
2
+
3
+ module ECDSA
4
+ class Point
5
+ attr_reader :group
6
+
7
+ attr_reader :x
8
+
9
+ attr_reader :y
10
+
11
+ def initialize(group, *args)
12
+ @group = group
13
+
14
+ if args == [:infinity]
15
+ @infinity = true
16
+ # leave @x and @y nil
17
+ else
18
+ x, y = args
19
+ raise ArgumentError, "Invalid x: #{x.inspect}" if !x.is_a? Integer
20
+ raise ArgumentError, "Invalid y: #{y.inspect}" if !y.is_a? Integer
21
+
22
+ @x = x
23
+ @y = y
24
+ end
25
+ end
26
+
27
+ def coords
28
+ [x, y]
29
+ end
30
+
31
+ def add_to_point(point)
32
+ check_group! point
33
+
34
+ # Assertions:
35
+ # raise "point given (#{point.inspect}) does not belong to #{group.name}" if !group.include?(point)
36
+ # raise "point (#{inspect}) does not belong to #{group.name}" if !group.include?(self)
37
+
38
+ # SEC1, section 2.2.1, rules 1 and 2
39
+ return point if infinity?
40
+ return self if point.infinity?
41
+
42
+ # SEC1, section 2.2.1, rule 3
43
+ return group.infinity_point if x == point.x && y == field.mod(-point.y)
44
+
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)
49
+ sum_y = field.mod(gamma * (x - sum_x) - y)
50
+ return self.class.new(group, sum_x, sum_y)
51
+ end
52
+
53
+ # SEC2, section 2.2.1, rule 5
54
+ return double if self == point
55
+
56
+ raise "Failed to add #{inspect} to #{point.inspect}: No addition rules matched."
57
+ end
58
+
59
+ def negate
60
+ return self if infinity?
61
+ self.class.new(group, x, field.mod(-y))
62
+ end
63
+
64
+ def double
65
+ gamma = field.mod((3 * x * x + @group.param_a) * field.inverse(2 * y))
66
+ new_x = field.mod(gamma * gamma - 2 * x)
67
+ new_y = field.mod(gamma * (x - new_x) - y)
68
+ self.class.new(group, new_x, new_y)
69
+ end
70
+
71
+ def multiply_by_scalar(i)
72
+ result = group.infinity_point
73
+ v = self
74
+ while i > 0
75
+ result = result.add_to_point(v) if i.odd?
76
+ v = v.double
77
+ i >>= 1
78
+ end
79
+ result
80
+ end
81
+
82
+ def eql?(other)
83
+ return false if !other.is_a?(Point) || other.group != group
84
+ x == other.x && y == other.y
85
+ end
86
+
87
+ def ==(other)
88
+ eql?(other)
89
+ end
90
+
91
+ def infinity?
92
+ @infinity == true
93
+ end
94
+
95
+ def inspect
96
+ if infinity?
97
+ '#<%s: %s, infinity>' % [self.class, group.name]
98
+ else
99
+ '#<%s: %s, 0x%x, 0x%x>' % [self.class, group.name, x, y]
100
+ end
101
+ end
102
+
103
+ private
104
+
105
+ def check_group!(point)
106
+ raise 'Mismatched groups.' if point.group != group
107
+ end
108
+
109
+ def field
110
+ group.field
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,81 @@
1
+ module ECDSA
2
+ class PrimeField
3
+ attr_reader :prime
4
+
5
+ def initialize(prime)
6
+ raise ArgumentError, "Invalid prime #{prime.inspect}" if !prime.is_a?(Integer)
7
+ @prime = prime
8
+ end
9
+
10
+ def include?(e)
11
+ e.is_a?(Integer) && e >= 0 && e < prime
12
+ end
13
+
14
+ def mod(num)
15
+ num % prime
16
+ end
17
+
18
+ # http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
19
+ def inverse(num)
20
+ raise ArgumentError, '0 has no multiplicative inverse.' if num.zero?
21
+
22
+ # For every i, we make sure that num * s[i] + prime * t[i] = r[i].
23
+ # Eventually r[i] will equal 1 because gcd(num, prime) is always 1.
24
+ # At that point, s[i] is the multiplicative inverse of num in the field.
25
+
26
+ remainders = [num, prime]
27
+ s = [1, 0]
28
+ t = [0, 1]
29
+ arrays = [remainders, s, t]
30
+ while remainders.last > 0
31
+ quotient = remainders[-2] / remainders[-1]
32
+ arrays.each do |array|
33
+ array << array[-2] - quotient * array[-1]
34
+ end
35
+ end
36
+
37
+ raise 'Inversion bug: remainder is not than 1.' if remainders[-2] != 1
38
+ mod s[-2]
39
+ end
40
+
41
+ # Computes n raised to the power m.
42
+ # This algorithm uses the same idea as Point#multiply_by_scalar.
43
+ def power(n, m)
44
+ result = 1
45
+ v = n
46
+ while m > 0
47
+ result = mod result * v if m.odd?
48
+ v = square v
49
+ m >>= 1
50
+ end
51
+ result
52
+ end
53
+
54
+ # Computes n^2.
55
+ def square(n)
56
+ mod n * n
57
+ end
58
+
59
+ def square_roots(n)
60
+ raise ArgumentError, "Not a member of the field: #{n}." if !include?(n)
61
+ if (prime % 4) == 3
62
+ square_roots_for_p_3_mod_4(n)
63
+ else
64
+ raise NotImplementedError, 'Square root is only implemented in fields where the prime is equal to 3 mod 4.'
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ # This is Algorithm 1 from http://math.stanford.edu/~jbooher/expos/sqr_qnr.pdf
71
+ # The algorithm assumes that its input actually does have a square root.
72
+ # To get around that, we double check the answer after running the algorithm to make
73
+ # sure it works.
74
+ def square_roots_for_p_3_mod_4(n)
75
+ candidate = power n, (prime + 1) / 4
76
+ return [] if square(candidate) != n
77
+ return [candidate] if candidate.zero?
78
+ [candidate, mod(-candidate)].sort
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,37 @@
1
+ module ECDSA
2
+ class SZeroError < StandardError
3
+ end
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)
10
+ # 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
13
+
14
+ # Step 2
15
+ xr = r_point.x
16
+
17
+ # Step 3
18
+ point_field = PrimeField.new(group.order)
19
+ r = point_field.mod(xr)
20
+
21
+ # Step 4, calculating the hash, was already performed by the caller.
22
+
23
+ # Step 5
24
+ e = normalize_digest(digest, group.bit_length)
25
+
26
+ # Step 6
27
+ s = point_field.mod(point_field.inverse(k) * (e + r * private_key))
28
+
29
+ if s.zero?
30
+ # We need to go back to step 1, so the caller should generate another
31
+ # random number k and try again.
32
+ return nil
33
+ end
34
+
35
+ Signature.new r, s
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ module ECDSA
2
+ class Signature
3
+ attr_reader :r
4
+ attr_reader :s
5
+
6
+ def initialize(r, s)
7
+ @r, @s = r, s
8
+ r.is_a?(Integer) or raise ArgumentError, 'r is not an integer.'
9
+ s.is_a?(Integer) or raise ArgumentError, 's is not an integer.'
10
+ end
11
+
12
+ def components
13
+ [r, s]
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,48 @@
1
+ module ECDSA
2
+ class InvalidSignatureError < StandardError
3
+ end
4
+
5
+ # Algorithm taken from http://www.secg.org/collateral/sec1_final.pdf Section 4.1.4.
6
+ def self.valid_signature?(public_key, digest, signature)
7
+ check_signature! public_key, digest, signature
8
+ rescue InvalidSignatureError
9
+ false
10
+ end
11
+
12
+ def self.check_signature!(public_key, digest, signature)
13
+ group = public_key.group
14
+ field = group.field
15
+
16
+ # 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)
19
+ raise InvalidSignatureError, 'r is zero.' if signature.r.zero?
20
+ raise InvalidSignatureError, 's is zero.' if signature.s.zero?
21
+
22
+ # Step 2 was already performed when the digest of the message was computed.
23
+
24
+ # Step 3: Convert octet string to number and take leftmost bits.
25
+ e = normalize_digest(digest, group.bit_length)
26
+
27
+ # Step 4
28
+ point_field = PrimeField.new(group.order)
29
+ s_inverted = point_field.inverse(signature.s)
30
+ u1 = point_field.mod(e * s_inverted)
31
+ u2 = point_field.mod(signature.r * s_inverted)
32
+
33
+ # Step 5
34
+ r = group.generator.multiply_by_scalar(u1).add_to_point public_key.multiply_by_scalar(u2)
35
+ raise InvalidSignatureError, 'R is infinity in step 5.' if r.infinity?
36
+
37
+ # Step 6
38
+ xr = r.x
39
+
40
+ # Step 7
41
+ v = point_field.mod xr
42
+
43
+ # Step 8
44
+ raise InvalidSignatureError, 'v does not equal r.' if v != signature.r
45
+
46
+ true
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module ECDSA
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ecdsa
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Grayson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-18 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: davidegrayson@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/ecdsa/format/decode_error.rb
20
+ - lib/ecdsa/format/field_element_octet_string.rb
21
+ - lib/ecdsa/format/integer_octet_string.rb
22
+ - lib/ecdsa/format/point_octet_string.rb
23
+ - lib/ecdsa/format/signature_der_string.rb
24
+ - lib/ecdsa/format.rb
25
+ - lib/ecdsa/group/nistp192.rb
26
+ - lib/ecdsa/group/nistp224.rb
27
+ - lib/ecdsa/group/nistp256.rb
28
+ - lib/ecdsa/group/nistp384.rb
29
+ - lib/ecdsa/group/nistp521.rb
30
+ - lib/ecdsa/group/secp112r1.rb
31
+ - lib/ecdsa/group/secp112r2.rb
32
+ - lib/ecdsa/group/secp128r1.rb
33
+ - lib/ecdsa/group/secp128r2.rb
34
+ - lib/ecdsa/group/secp160k1.rb
35
+ - lib/ecdsa/group/secp160r1.rb
36
+ - lib/ecdsa/group/secp160r2.rb
37
+ - lib/ecdsa/group/secp192k1.rb
38
+ - lib/ecdsa/group/secp192r1.rb
39
+ - lib/ecdsa/group/secp224k1.rb
40
+ - lib/ecdsa/group/secp224r1.rb
41
+ - lib/ecdsa/group/secp256k1.rb
42
+ - lib/ecdsa/group/secp256r1.rb
43
+ - lib/ecdsa/group/secp384r1.rb
44
+ - lib/ecdsa/group/secp521r1.rb
45
+ - lib/ecdsa/group.rb
46
+ - lib/ecdsa/point.rb
47
+ - lib/ecdsa/prime_field.rb
48
+ - lib/ecdsa/sign.rb
49
+ - lib/ecdsa/signature.rb
50
+ - lib/ecdsa/verify.rb
51
+ - lib/ecdsa/version.rb
52
+ - lib/ecdsa.rb
53
+ - Gemfile
54
+ - README.md
55
+ - LICENSE.txt
56
+ homepage: https://github.com/pololu/rpicsim
57
+ licenses:
58
+ - MIT
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '2'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 2.0.0
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: This gem implements the Ellipctic Curve Digital Signature Algorithm (ECDSA)
80
+ almost entirely in pure Ruby.
81
+ test_files: []
82
+ has_rdoc: