starkbank-ecdsa 0.0.5 → 2.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
  SHA256:
3
- metadata.gz: 552ac4aee46c31ad3dcc11dac2022b7a458925d718fc22ac31c5349a580e441d
4
- data.tar.gz: 94d725e7728047557a4c032d5f27c9174b0ace61e76e54bdff481d28fc55ccf3
3
+ metadata.gz: 4d4a76bc728ec00f073424e79a9e72d75d39aa863074fff03fd5c35694d221f2
4
+ data.tar.gz: d8612f17a0eb7b9d7e5d735f7fc164ee288e770ba5d2b4bc62c35fcbda97088e
5
5
  SHA512:
6
- metadata.gz: 657954155c41471d9ecf3799d705c7183cb0beb888de65f9e609f7bad729e6188cd0fdd9f67ee7193ffb2807b679fa9ee1f3f08687b0a0ba9ff32dede7cd3476
7
- data.tar.gz: bb5e12f6c6f9413050f74f7acb6a204a75a0af1710689bca44a3af78783d6dec19315a623923c0c370bf23d4eded548d89e187b937cce1c8c04d63c0623e9d5a
6
+ metadata.gz: 88d83c16ca1e416998cf5411dd45ca4cd9e62af3841dcea5981a2fa8dedbc86ea74a8ef91ceb44db6e5192e36af65c5877f32d373ccffd154c4472951d379fcf
7
+ data.tar.gz: 4332d9b28484fe4c09708147494ea33c3caa48f263824a72ef846888a32cd27bad585a98560a8f7883c5be88394da72318e44a144e0b1b4ecfbde148812de058
data/lib/curve.rb ADDED
@@ -0,0 +1,94 @@
1
+ module EllipticCurve
2
+ #
3
+ # Elliptic Curve Equation
4
+ #
5
+ # y^2 = x^3 + A*x + B (mod P)
6
+ #
7
+ module Curve
8
+ class CurveFp
9
+ attr_accessor :a, :b, :p, :n, :g, :name, :oid, :nistName
10
+
11
+ def initialize(a, b, p, n, gx, gy, name, oid, nistName=nil)
12
+ @a = a
13
+ @b = b
14
+ @p = p
15
+ @n = n
16
+ @g = Point.new(gx, gy)
17
+ @name = name
18
+ @oid = oid
19
+ @nistName = nistName
20
+ end
21
+
22
+ def contains(p)
23
+ # Verify if the point `p` is on the curve
24
+ # :param p: point p = Point(x, y)
25
+ # :return: boolean
26
+ if not (0 <= p.x and p.x <= @p - 1)
27
+ return false
28
+ end
29
+ if not (0 <= p.y and p.y <= @p - 1)
30
+ return false
31
+ end
32
+ if (p.y ** 2 - (p.x ** 3 + @a * p.x + @b)) % @p != 0
33
+ return false
34
+ end
35
+ return true
36
+ end
37
+
38
+ def length
39
+ return (1 + ("%x" % @n).length).div(2)
40
+ end
41
+
42
+ def y(x, isEven)
43
+ ySquared = (x.pow(3, @p) + @a * x + @b) % @p
44
+ y = Math::modularSquareRoot(ySquared, @p)
45
+ if isEven != (y % 2 == 0)
46
+ y = @p - y
47
+ end
48
+ return y
49
+ end
50
+
51
+ end
52
+
53
+ @_curvesByOid = { }
54
+
55
+ def self.add(curve)
56
+ @_curvesByOid[curve.oid] = curve
57
+ end
58
+
59
+ def self.getbyOid(oid)
60
+ if not @_curvesByOid.include?(oid)
61
+ raise Exception.new("Unknown curve oid: #{oid}; The following are registered: #{@_curvesByOid.map{|k,v| v.name}}")
62
+ end
63
+ return @_curvesByOid[oid]
64
+ end
65
+
66
+ SECP256K1 = CurveFp.new(
67
+ 0x0000000000000000000000000000000000000000000000000000000000000000,
68
+ 0x0000000000000000000000000000000000000000000000000000000000000007,
69
+ 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,
70
+ 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141,
71
+ 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
72
+ 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8,
73
+ "secp256k1",
74
+ [1, 3, 132, 0, 10]
75
+ )
76
+
77
+ PRIME256V1 = CurveFp.new(
78
+ 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc,
79
+ 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b,
80
+ 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff,
81
+ 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551,
82
+ 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
83
+ 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5,
84
+ "prime256v1",
85
+ [1, 2, 840, 10045, 3, 1, 7],
86
+ "p-256",
87
+ )
88
+
89
+ P256 = PRIME256V1
90
+
91
+ self.add(PRIME256V1)
92
+ self.add(SECP256K1)
93
+ end
94
+ end
data/lib/ecdsa.rb CHANGED
@@ -1,32 +1,50 @@
1
1
  require 'digest'
2
- require 'openssl'
3
- require_relative './signature'
4
2
 
5
3
 
6
4
  module EllipticCurve
7
-
8
5
  module Ecdsa
9
-
10
6
  def self.sign(message, privateKey, hashfunc=nil)
11
- if hashfunc.nil?
12
- message = Digest::SHA256.digest(message)
13
- else
14
- message = hashfunc(message)
7
+ if hashfunc.nil? then hashfunc = lambda{ |x| Digest::SHA256.digest(x) } end
8
+ byteMessage = hashfunc.call(message)
9
+ numberMessage = Utils::Binary.numberFromByteString(byteMessage)
10
+ curve = privateKey.curve
11
+
12
+ r, s, randSignPoint = 0, 0, nil
13
+ while r == 0 or s == 0
14
+ randNum = Utils::RandomInteger.between(1, curve.n - 1)
15
+ randSignPoint = Math.multiply(curve.g, randNum, curve.n, curve.a, curve.p)
16
+ r = randSignPoint.x % curve.n
17
+ s = ((numberMessage + r * privateKey.secret) * (Math.inv(randNum, curve.n))) % curve.n
15
18
  end
16
-
17
- signature = privateKey.openSslPrivateKey.dsa_sign_asn1(message)
18
- return Signature.new(signature)
19
+ recoveryId = randSignPoint.y & 1
20
+ if randSignPoint.y > curve.n
21
+ recoveryId += 2
22
+ end
23
+ return Signature.new(r, s, recoveryId)
19
24
  end
20
25
 
21
26
  def self.verify(message, signature, publicKey, hashfunc=nil)
22
- if hashfunc.nil?
23
- message = Digest::SHA256.digest(message)
24
- else
25
- message = hashfunc(message)
27
+ if hashfunc.nil? then hashfunc = lambda{ |x| Digest::SHA256.digest(x) } end
28
+ byteMessage = hashfunc.call(message)
29
+ numberMessage = Utils::Binary.numberFromByteString(byteMessage)
30
+
31
+ curve = publicKey.curve
32
+ r = signature.r
33
+ s = signature.s
34
+ if not (1 <= r and r <= curve.n - 1)
35
+ return false
26
36
  end
27
- return publicKey.openSslPublicKey.dsa_verify_asn1(message, signature.toDer())
37
+ if not (1 <= s and s <= curve.n - 1)
38
+ return false
39
+ end
40
+ inv = Math.inv(s, curve.n)
41
+ u1 = Math.multiply(curve.g, (numberMessage * inv) % curve.n, curve.n, curve.a, curve.p)
42
+ u2 = Math.multiply(publicKey.point, (r * inv) % curve.n, curve.n, curve.a, curve.p)
43
+ v = Math.add(u1, u2, curve.a, curve.p)
44
+ if v.isAtInfinity
45
+ return false
46
+ end
47
+ return v.x % curve.n == r
28
48
  end
29
-
30
49
  end
31
-
32
- end
50
+ end
data/lib/math.rb ADDED
@@ -0,0 +1,167 @@
1
+ module EllipticCurve
2
+ class Math
3
+ def self.modularSquareRoot(value, prime)
4
+ # :param value: Value to calculate the square root
5
+ # :param prime: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
6
+ # :return: Square root of the value
7
+ return value.pow((prime + 1).div(4), prime)
8
+ end
9
+
10
+ def self.multiply(p, n, order, coeff, prime)
11
+ # Fast way to multiply point and scalar in elliptic curves
12
+
13
+ # :param p: First Point to mutiply
14
+ # :param n: Scalar to mutiply
15
+ # :param N: Order of the elliptic curve
16
+ # :param A: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
17
+ # :param P: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
18
+ # :return: Point that represents the sum of First and Second Point
19
+ return self._fromJacobian(
20
+ self._jacobianMultiply(self._toJacobian(p), n, order, coeff, prime), prime
21
+ )
22
+ end
23
+
24
+ def self.add(p, q, coeff, prime)
25
+ # Fast way to add two points in elliptic curves
26
+
27
+ # :param p: First Point you want to add
28
+ # :param q: Second Point you want to add
29
+ # :param P: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
30
+ # :param A: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
31
+ # :return: Point that represents the sum of First and Second Point
32
+ return self._fromJacobian(
33
+ self._jacobianAdd(self._toJacobian(p), self._toJacobian(q), coeff, prime), prime
34
+ )
35
+ end
36
+
37
+ def self.inv(x, n)
38
+ # Extended Euclidean Algorithm. It's the 'division' in elliptic curves
39
+
40
+ # :param x: Divisor
41
+ # :param n: Mod for division
42
+ # :return: Value representing the division
43
+ if x == 0 then return 0 end
44
+
45
+ lm = 1
46
+ hm = 0
47
+ low = x % n
48
+ high = n
49
+
50
+ while low > 1
51
+ r = high.div(low)
52
+ nm = hm - lm * r
53
+ nw = high - low * r
54
+ high = low
55
+ hm = lm
56
+ low = nw
57
+ lm = nm
58
+ end
59
+ return lm % n
60
+ end
61
+
62
+ def self._toJacobian(p)
63
+ # Convert point to Jacobian coordinates
64
+
65
+ # :param p: First Point you want to add
66
+ # :return: Point in Jacobian coordinates
67
+ return Point.new(p.x, p.y, 1)
68
+ end
69
+
70
+ def self._fromJacobian(p, prime)
71
+ # Convert point back from Jacobian coordinates
72
+
73
+ # :param p: First Point you want to add
74
+ # :param P: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
75
+ # :return: Point in default coordinates
76
+ z = self.inv(p.z, prime)
77
+ x = (p.x * z ** 2) % prime
78
+ y = (p.y * z ** 3) % prime
79
+
80
+ return Point.new(x, y, 0)
81
+ end
82
+
83
+ def self._jacobianDouble(p, coeff, prime)
84
+ # Double a point in elliptic curves
85
+
86
+ # :param p: Point you want to double
87
+ # :param A: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
88
+ # :param P: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
89
+ # :return: Point that represents the sum of First and Second Point
90
+ if p.y == 0 then return Point.new(0, 0, 0) end
91
+
92
+ ysq = (p.y ** 2) % prime
93
+ s = (4 * p.x * ysq) % prime
94
+ m = (3 * p.x ** 2 + coeff * p.z ** 4) % prime
95
+ nx = (m ** 2 - 2 * s) % prime
96
+ ny = (m * (s - nx) - 8 * ysq ** 2) % prime
97
+ nz = (2 * p.y * p.z) % prime
98
+
99
+ return Point.new(nx, ny, nz)
100
+ end
101
+
102
+ def self._jacobianAdd(p, q, coeff, prime)
103
+ # Add two points in elliptic curves
104
+
105
+ # :param p: First Point you want to add
106
+ # :param q: Second Point you want to add
107
+ # :param A: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
108
+ # :param P: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
109
+ # :return: Point that represents the sum of First and Second Point
110
+ if p.y == 0 then return q end
111
+ if q.y == 0 then return p end
112
+
113
+ u1 = (p.x * q.z ** 2) % prime
114
+ u2 = (q.x * p.z ** 2) % prime
115
+ s1 = (p.y * q.z ** 3) % prime
116
+ s2 = (q.y * p.z ** 3) % prime
117
+
118
+ if u1 == u2
119
+ if s1 != s2 then return Point.new(0, 0, 1) end
120
+ return self._jacobianDouble(p, coeff, prime)
121
+ end
122
+
123
+ h = u2 - u1
124
+ r = s2 - s1
125
+ h2 = (h * h) % prime
126
+ h3 = (h * h2) % prime
127
+ u1h2 = (u1 * h2) % prime
128
+ nx = (r ** 2 - h3 - 2 * u1h2) % prime
129
+ ny = (r * (u1h2 - nx) - s1 * h3) % prime
130
+ nz = (h * p.z * q.z) % prime
131
+
132
+ return Point.new(nx, ny, nz)
133
+ end
134
+
135
+ def self._jacobianMultiply(p, n, order, coeff, prime)
136
+ # Multiply point and scalar in elliptic curves
137
+
138
+ # :param p: First Point to mutiply
139
+ # :param n: Scalar to mutiply
140
+ # :param N: Order of the elliptic curve
141
+ # :param P: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
142
+ # :param A: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
143
+ # :return: Point that represents the sum of First and Second Point
144
+ if p.y == 0 or n == 0
145
+ return Point.new(0, 0, 1)
146
+ end
147
+
148
+ if n == 1
149
+ return p
150
+ end
151
+
152
+ if n < 0 or n >= order
153
+ return self._jacobianMultiply(p, n % order, order, coeff, prime)
154
+ end
155
+
156
+ if (n % 2) == 0
157
+ return self._jacobianDouble(
158
+ self._jacobianMultiply(p, n.div(2), order, coeff, prime), coeff, prime
159
+ )
160
+ end
161
+
162
+ return self._jacobianAdd(
163
+ self._jacobianDouble(self._jacobianMultiply(p, n.div(2), order, coeff, prime), coeff, prime), p, coeff, prime
164
+ )
165
+ end
166
+ end
167
+ end
data/lib/point.rb ADDED
@@ -0,0 +1,19 @@
1
+ module EllipticCurve
2
+ class Point
3
+ def initialize(x=0, y=0, z=0)
4
+ @x = x
5
+ @y = y
6
+ @z = z
7
+ end
8
+
9
+ attr_accessor :x, :y, :z
10
+
11
+ def to_s
12
+ return "(#{@x}, #{@y}, #{@z})"
13
+ end
14
+
15
+ def isAtInfinity()
16
+ return @y == 0
17
+ end
18
+ end
19
+ end
data/lib/privatekey.rb CHANGED
@@ -1,53 +1,68 @@
1
- require "openssl"
2
- require "base64"
3
- require_relative "publickey"
4
-
5
-
6
1
  module EllipticCurve
7
-
8
2
  class PrivateKey
3
+ PemTemplate = "-----BEGIN EC PRIVATE KEY-----\n{content}\n-----END EC PRIVATE KEY-----"
4
+ private_constant :PemTemplate
9
5
 
10
- def initialize(curve="secp256k1", openSslKey=nil)
11
- if openSslKey.nil?
12
- @openSslPrivateKey = OpenSSL::PKey::EC.new(curve)
13
- @openSslPrivateKey.generate_key
14
- else
15
- @openSslPrivateKey = openSslKey
16
- end
17
- end
18
-
19
- attr_reader :openSslPrivateKey
6
+ attr_accessor :curve, :secret
20
7
 
8
+ def initialize(curve=Curve::SECP256K1, secret=nil)
9
+ @curve = curve
10
+ @secret = secret ? secret : Utils::RandomInteger.between(1, @curve.n - 1)
11
+ end
12
+
21
13
  def publicKey
22
- dupKey = OpenSSL::PKey::EC.new(@openSslPrivateKey.to_der())
23
- dupKey.private_key = nil
24
- return PublicKey.new(dupKey)
14
+ curve = @curve
15
+ publicPoint = Math.multiply(
16
+ curve.g,
17
+ @secret,
18
+ curve.n,
19
+ curve.a,
20
+ curve.p
21
+ )
22
+ return PublicKey.new(publicPoint, curve)
25
23
  end
26
24
 
27
25
  def toString
28
- return Base64.encode64(self.toDer())
26
+ return Utils::Binary.hexFromInt(@secret)
29
27
  end
30
28
 
31
29
  def toDer
32
- return @openSslPrivateKey.to_der()
30
+ publicKeyString = self.publicKey.toString(true)
31
+ hexadecimal = Utils::Der.encodeConstructed(
32
+ Utils::Der.encodePrimitive(Utils::Der::DerFieldType.integer, 1),
33
+ Utils::Der.encodePrimitive(Utils::Der::DerFieldType.octetString, Utils::Binary.hexFromInt(@secret)),
34
+ Utils::Der.encodePrimitive(Utils::Der::DerFieldType.oidContainer, Utils::Der.encodePrimitive(Utils::Der::DerFieldType.object, @curve.oid)),
35
+ Utils::Der.encodePrimitive(Utils::Der::DerFieldType.publicKeyPointContainer, Utils::Der.encodePrimitive(Utils::Der::DerFieldType.bitString, publicKeyString))
36
+ )
37
+ return Utils::Binary.byteStringFromHex(hexadecimal)
33
38
  end
34
39
 
35
40
  def toPem
36
- return @openSslPrivateKey.to_pem()
41
+ der = self.toDer()
42
+ return Utils::Pem.create(Utils::Binary.base64FromByteString(der), PemTemplate)
37
43
  end
38
44
 
39
45
  def self.fromPem(string)
40
- return PrivateKey.new(nil, OpenSSL::PKey::EC.new(string))
46
+ privateKeyPem = Utils::Pem.getContent(string, PemTemplate)
47
+ return self.fromDer(Utils::Binary.byteStringFromBase64(privateKeyPem))
41
48
  end
42
49
 
43
50
  def self.fromDer(string)
44
- return PrivateKey.new(nil, OpenSSL::PKey::EC.new(string))
45
- end
46
-
47
- def self.fromString(string)
48
- return PrivateKey.new(nil, OpenSSL::PKey::EC.new(Base64.decode64(string)))
51
+ hexadecimal = Utils::Binary.hexFromByteString(string)
52
+ privateKeyFlag, secretHex, curveData, publicKeyString = Utils::Der.parse(hexadecimal)[0]
53
+ if privateKeyFlag != 1
54
+ raise Exception.new("Private keys should start with a '1' flag, but a '#{privateKeyFlag}' was found instead")
55
+ end
56
+ curve = Curve.getbyOid(curveData[0])
57
+ privateKey = self.fromString(secretHex, curve)
58
+ if privateKey.publicKey.toString(true) != publicKeyString[0]
59
+ raise Exception.new("The public key described inside the private key file doesn't match the actual public key of the pair")
60
+ end
61
+ return privateKey
49
62
  end
50
63
 
64
+ def self.fromString(string, curve=Curve::SECP256K1)
65
+ return PrivateKey.new(curve, Utils::Binary.intFromHex(string))
66
+ end
51
67
  end
52
-
53
- end
68
+ end
data/lib/publickey.rb CHANGED
@@ -1,37 +1,108 @@
1
1
  module EllipticCurve
2
-
3
2
  class PublicKey
3
+ PemTemplate = "-----BEGIN PUBLIC KEY-----\n{content}\n-----END PUBLIC KEY-----"
4
+ EcdsaPublicKeyOid = [1, 2, 840, 10045, 2, 1]
5
+ EvenTag = "02"
6
+ OddTag = "03"
7
+ private_constant :PemTemplate, :EcdsaPublicKeyOid, :EvenTag, :OddTag
8
+
9
+ attr_accessor :point, :curve
4
10
 
5
- def initialize(openSslPublicKey)
6
- @openSslPublicKey = openSslPublicKey
11
+ def initialize(point, curve)
12
+ @point = point
13
+ @curve = curve
7
14
  end
15
+
16
+ def toString encoded=false
17
+ baseLength = 2 * @curve.length
8
18
 
9
- attr_reader :openSslPublicKey
19
+ xHex = Utils::Binary.hexFromInt(@point.x).rjust(baseLength, "0")
20
+ yHex = Utils::Binary.hexFromInt(@point.y).rjust(baseLength, "0")
21
+ string = xHex + yHex
22
+ if encoded
23
+ return "0004" + string
24
+ end
25
+ return string
26
+ end
10
27
 
11
- def toString
12
- return Base64.encode64(self.toDer())
28
+ def toCompressed
29
+ baseLength = 2 * @curve.length
30
+ parityTag = @point.y % 2 == 0 ? EvenTag : OddTag
31
+ xHex = Utils::Binary.hexFromInt(@point.x).rjust(baseLength, "0")
32
+ return parityTag + xHex
13
33
  end
14
34
 
15
35
  def toDer
16
- @openSslPublicKey.to_der()
36
+ @hexadecimal = Utils::Der.encodeConstructed(
37
+ Utils::Der.encodeConstructed(
38
+ Utils::Der.encodePrimitive(Utils::Der::DerFieldType.object, EcdsaPublicKeyOid),
39
+ Utils::Der.encodePrimitive(Utils::Der::DerFieldType.object, @curve.oid)
40
+ ),
41
+ Utils::Der.encodePrimitive(Utils::Der::DerFieldType.bitString, self.toString(true))
42
+ )
43
+ return Utils::Binary.byteStringFromHex(@hexadecimal)
17
44
  end
18
45
 
19
46
  def toPem
20
- @openSslPublicKey.to_pem()
47
+ der = self.toDer()
48
+ return Utils::Pem.create(Utils::Binary.base64FromByteString(der), PemTemplate)
21
49
  end
22
50
 
23
51
  def self.fromPem(string)
24
- return PublicKey.new(OpenSSL::PKey::EC.new(string))
52
+ publicKeyPem = Utils::Pem.getContent(string, PemTemplate)
53
+ return self.fromDer(Utils::Binary.byteStringFromBase64(publicKeyPem))
25
54
  end
26
55
 
27
56
  def self.fromDer(string)
28
- return PublicKey.new(OpenSSL::PKey::EC.new(string))
57
+ hexadecimal = Utils::Binary.hexFromByteString(string)
58
+ curveData, pointString = Utils::Der.parse(hexadecimal)[0]
59
+ publicKeyOid, curveOid = curveData
60
+ if publicKeyOid != EcdsaPublicKeyOid
61
+ raise Exception.new("The Public Key Object Identifier (OID) should be #{EcdsaPublicKeyOid}, but #{publicKeyOid} was found instead")
62
+ end
63
+ curve = Curve.getbyOid(curveOid)
64
+ return self.fromString(pointString, curve)
29
65
  end
30
66
 
31
- def self.fromString(string)
32
- return PublicKey.new(OpenSSL::PKey::EC.new(Base64.decode64(string)))
67
+ def self.fromString(string, curve=Curve::SECP256K1, validatePoint=true)
68
+ baseLength = 2 * curve.length
69
+ if string.length > 2 * baseLength and string[0..3] == "0004"
70
+ string = string[4..-1]
71
+ end
72
+
73
+ xs = string[0..baseLength - 1]
74
+ ys = string[baseLength..-1]
75
+
76
+ p = Point.new(
77
+ Utils::Binary.intFromHex(xs),
78
+ Utils::Binary.intFromHex(ys)
79
+ )
80
+
81
+ publicKey = PublicKey.new(p, curve)
82
+ if not validatePoint
83
+ return publicKey
84
+ end
85
+ if p.isAtInfinity()
86
+ raise Exception.new("Public key point at infinity")
87
+ end
88
+ if not curve.contains(p)
89
+ raise Exception.new("Point (#{p.x}, #{p.y}) is not valid for curve #{curve.name}")
90
+ end
91
+ if not Math.multiply(p, curve.n, curve.n, curve.a, curve.p).isAtInfinity()
92
+ raise Exception.new("Point (#{p.x}, #{p.y}) * #{curve.name}.n is not at infinity")
93
+ end
94
+
95
+ return publicKey
33
96
  end
34
97
 
98
+ def self.fromCompressed(string, curve=Curve::SECP256K1)
99
+ parityTag, xHex = string[0..1], string[2..-1]
100
+ if not [EvenTag, OddTag].include? parityTag
101
+ raise Exception.new("Compressed string should start with 02 or 03")
102
+ end
103
+ x = Utils::Binary.intFromHex(xHex)
104
+ y = curve.y(x=x, isEven=parityTag == EvenTag)
105
+ return PublicKey.new(point=Point.new(x, y), curve=curve)
106
+ end
35
107
  end
36
-
37
- end
108
+ end
data/lib/signature.rb CHANGED
@@ -1,35 +1,51 @@
1
- require "base64"
2
- require "openssl"
3
-
4
-
5
1
  module EllipticCurve
6
-
7
2
  class Signature
8
-
9
- def initialize(der)
10
- @der = der
11
- decoded = OpenSSL::ASN1.decode(der).value
12
- @r = decoded[0].value
13
- @s = decoded[1].value
3
+ attr_reader :r, :s, :recoveryId
4
+
5
+ def initialize(r, s, recoveryId=nil)
6
+ @r = r
7
+ @s = s
8
+ @recoveryId = recoveryId
14
9
  end
15
10
 
16
- attr_reader :r, :s
11
+ def toDer(withRecoveryId=false)
12
+ hexadecimal = self._toString
13
+ encodedSequence = Utils::Binary.byteStringFromHex(hexadecimal)
14
+ if not withRecoveryId
15
+ return encodedSequence
16
+ end
17
+ return (27 + @recoveryId).chr + encodedSequence
18
+ end
17
19
 
18
- def toDer
19
- return @der
20
+ def toBase64(withRecoveryId=false)
21
+ return Utils::Binary.base64FromByteString(self.toDer(withRecoveryId))
20
22
  end
21
23
 
22
- def toBase64
23
- Base64.encode64(self.toDer()).gsub("\n", "")
24
+ def self.fromDer(string, recoveryByte=false)
25
+ @recoveryId = nil
26
+ if recoveryByte
27
+ @recoveryId = string[0].ord - 27
28
+ string = string[1..-1]
29
+ end
30
+ hexadecimal = Utils::Binary.hexFromByteString(string)
31
+ return self._fromString(hexadecimal, @recoveryId)
24
32
  end
25
33
 
26
- def self.fromDer(string)
27
- return Signature.new(string)
34
+ def self.fromBase64(string, recoveryByte=false)
35
+ der = Utils::Binary.byteStringFromBase64(string)
36
+ return self.fromDer(der, recoveryByte)
28
37
  end
29
38
 
30
- def self.fromBase64(string)
31
- self.fromDer(Base64.decode64(string))
39
+ def _toString
40
+ return Utils::Der.encodeConstructed(
41
+ Utils::Der.encodePrimitive(Utils::Der::DerFieldType.integer, @r),
42
+ Utils::Der.encodePrimitive(Utils::Der::DerFieldType.integer, @s)
43
+ )
32
44
  end
33
45
 
46
+ def self._fromString(string, recoveryId=nil)
47
+ @r, @s = Utils::Der.parse(string)[0]
48
+ return Signature.new(@r, @s, recoveryId)
49
+ end
34
50
  end
35
- end
51
+ end
@@ -1,5 +1,13 @@
1
+ require_relative "utils/pem"
2
+ require_relative "utils/binary"
3
+ require_relative "utils/oid"
4
+ require_relative "utils/integer"
5
+ require_relative "utils/der"
6
+ require_relative "utils/file"
1
7
  require_relative "signature"
2
8
  require_relative "publickey"
9
+ require_relative "math"
10
+ require_relative "point"
11
+ require_relative "curve"
3
12
  require_relative "privatekey"
4
13
  require_relative "ecdsa"
5
- require_relative "utils/file"
@@ -0,0 +1,44 @@
1
+ require "base64"
2
+
3
+
4
+ module EllipticCurve
5
+ module Utils
6
+ class Binary
7
+ def self.hexFromInt(number)
8
+ hexadecimal = number.to_s(16)
9
+ if hexadecimal.length % 2 == 1
10
+ hexadecimal = "0" + hexadecimal
11
+ end
12
+ return hexadecimal
13
+ end
14
+
15
+ def self.intFromHex(hexadecimal)
16
+ return hexadecimal.to_i(16)
17
+ end
18
+
19
+ def self.hexFromByteString(bytes)
20
+ return bytes.unpack("H*")[0]
21
+ end
22
+
23
+ def self.byteStringFromHex(hexadecimal)
24
+ return [hexadecimal].pack("H*")
25
+ end
26
+
27
+ def self.numberFromByteString(bytes)
28
+ return bytes.unpack("C*").reduce(0) { |number, byte| number * 256 + byte }
29
+ end
30
+
31
+ def self.base64FromByteString(byteString)
32
+ return Base64.encode64(byteString).gsub("\n", "")
33
+ end
34
+
35
+ def self.byteStringFromBase64(base64)
36
+ return Base64.decode64(base64)
37
+ end
38
+
39
+ def self.bitsFromHex(hexadecimal)
40
+ return intFromHex(hexadecimal).to_s(2).rjust(hexadecimal.length * 4, "0")
41
+ end
42
+ end
43
+ end
44
+ end
data/lib/utils/der.rb ADDED
@@ -0,0 +1,184 @@
1
+ module EllipticCurve
2
+ module Utils
3
+ class Der
4
+ module DerFieldType
5
+ @integer = "integer"
6
+ @bitString = "bitString"
7
+ @octetString = "octetString"
8
+ @null = "null"
9
+ @object = "object"
10
+ @printableString = "printableString"
11
+ @utcTime = "utcTime"
12
+ @sequence = "sequence"
13
+ @set = "set"
14
+ @oidContainer = "oidContainer"
15
+ @publicKeyPointContainer = "publicKeyPointContainer"
16
+
17
+ class << self; attr_accessor :integer, :bitString, :octetString, :null, :object, :printableString, :utcTime, :sequence, :set, :oidContainer, :publicKeyPointContainer; end
18
+ end
19
+
20
+ @_hexTagToType = {
21
+ "02" => DerFieldType.integer,
22
+ "03" => DerFieldType.bitString,
23
+ "04" => DerFieldType.octetString,
24
+ "05" => DerFieldType.null,
25
+ "06" => DerFieldType.object,
26
+ "13" => DerFieldType.printableString,
27
+ "17" => DerFieldType.utcTime,
28
+ "30" => DerFieldType.sequence,
29
+ "31" => DerFieldType.set,
30
+ "a0" => DerFieldType.oidContainer,
31
+ "a1" => DerFieldType.publicKeyPointContainer
32
+ }
33
+
34
+ @_typeToHexTag = {}
35
+
36
+ @_hexTagToType.each { |k, v| @_typeToHexTag[v] = k }
37
+
38
+ def self.encodeConstructed(*encodedValues)
39
+ return self.encodePrimitive(DerFieldType.sequence, encodedValues.join(""))
40
+ end
41
+
42
+ def self.encodePrimitive(tagType, value)
43
+ if tagType == DerFieldType.integer
44
+ value = self._encodeInteger(value)
45
+ end
46
+ if tagType == DerFieldType.object
47
+ value = Utils::Oid.oidToHex(value)
48
+ end
49
+ return "#{@_typeToHexTag[tagType]}#{self._generateLengthBytes(value)}#{value}"
50
+ end
51
+
52
+ def self.parse(hexadecimal)
53
+ if hexadecimal.class == String && hexadecimal.empty?
54
+ return []
55
+ elsif not hexadecimal then
56
+ return []
57
+ end
58
+
59
+ typeByte, hexadecimal = hexadecimal[0..1], hexadecimal[2..-1]
60
+ length, lengthBytes = self._readLengthBytes(hexadecimal)
61
+ content = hexadecimal[lengthBytes..lengthBytes + length - 1]
62
+ hexadecimal = hexadecimal[lengthBytes + length..-1]
63
+
64
+ if content.length < length
65
+ raise Exception.new("missing bytes in DER parsing")
66
+ end
67
+
68
+ tagData = self._getTagData(typeByte)
69
+ if tagData[:isConstructed]
70
+ content = self.parse(content)
71
+ end
72
+
73
+ valueParser = {
74
+ DerFieldType.null => lambda { |content| self._parseNull(content) },
75
+ DerFieldType.object => lambda { |content| self._parseOid(content) },
76
+ DerFieldType.utcTime => lambda { |content| self._parseTime(content) },
77
+ DerFieldType.integer => lambda { |content| self._parseInteger(content) },
78
+ DerFieldType.printableString => lambda { |content| self._parseString(content) },
79
+ }.fetch(tagData[:type], lambda { |content| self._parseAny(content) })
80
+
81
+ return [valueParser.call(content)] + self.parse(hexadecimal)
82
+ end
83
+
84
+ def self._parseAny(hexadecimal)
85
+ return hexadecimal
86
+ end
87
+
88
+ def self._parseOid(hexadecimal)
89
+ return Utils::Oid.oidFromHex(hexadecimal)
90
+ end
91
+
92
+ def self._parseTime(hexadecimal)
93
+ string = self._parseString(hexadecimal)
94
+ return DateTime.strptime(string, "%y%m%d%H%M%SZ")
95
+ end
96
+
97
+ def self._parseString(hexadecimal)
98
+ return Utils::Binary.byteStringFromHex(hexadecimal)
99
+ end
100
+
101
+ def self._parseNull(_content)
102
+ return nil
103
+ end
104
+
105
+ def self._parseInteger(hexadecimal)
106
+ integer = Utils::Binary.intFromHex(hexadecimal)
107
+ bits = Utils::Binary.bitsFromHex(hexadecimal[0])
108
+ if bits[0] == "0" # negative numbers are encoded using two's complement
109
+ return integer
110
+ end
111
+ bitCount = 4 * hexadecimal.length
112
+ return integer - (2 ** bitCount)
113
+ end
114
+
115
+ def self._encodeInteger(number)
116
+ hexadecimal = Utils::Binary.hexFromInt(number.abs)
117
+ if number < 0
118
+ bitCount = hexadecimal.length * 4
119
+ twosComplement = (2 ** bitCount) + number
120
+ return Utils::Binary.hexFromInt(twosComplement)
121
+ end
122
+ bits = Utils::Binary.bitsFromHex(hexadecimal[0])
123
+ if bits[0] == "1"
124
+ hexadecimal = "00" + hexadecimal
125
+ end
126
+ return hexadecimal
127
+ end
128
+
129
+ def self._readLengthBytes(hexadecimal)
130
+ lengthBytes = 2
131
+ lengthIndicator = Utils::Binary.intFromHex(hexadecimal[0, lengthBytes])
132
+ isShortForm = lengthIndicator < 128 # checks if first bit of byte is 1 (a.k.a. short-form)
133
+ if isShortForm
134
+ length = lengthIndicator * 2
135
+ return length, lengthBytes
136
+ end
137
+
138
+ lengthLength = lengthIndicator - 128 # nullifies first bit of byte (only used as long-form flag)
139
+ if lengthLength == 0
140
+ raise Exception.new("indefinite length encoding located in DER")
141
+ end
142
+ lengthBytes += 2 * lengthLength
143
+ length = Utils::Binary.intFromHex(hexadecimal[2, lengthBytes]) * 2
144
+ return length, lengthBytes
145
+ end
146
+
147
+ def self._generateLengthBytes(hexadecimal)
148
+ size = hexadecimal.length.div(2)
149
+ length = Utils::Binary.hexFromInt(size)
150
+ if size < 128
151
+ return length.rjust(2, "0")
152
+ end
153
+ lengthLength = 128 + length.length.div(2)
154
+ return Utils::Binary.hexFromInt(lengthLength) + length
155
+ end
156
+
157
+ def self._getTagData(tag)
158
+ bits = Utils::Binary.bitsFromHex(tag)
159
+ bit8 = bits[0]
160
+ bit7 = bits[1]
161
+ bit6 = bits[2]
162
+
163
+ tagClass = {
164
+ "0" => {
165
+ "0" => "universal",
166
+ "1" => "application",
167
+ },
168
+ "1" => {
169
+ "0" => "context-specific",
170
+ "1" => "private",
171
+ },
172
+ }[bit8][bit7]
173
+
174
+ isConstructed = bit6 == "1"
175
+
176
+ return {
177
+ "class": tagClass,
178
+ "isConstructed": isConstructed,
179
+ "type": @_hexTagToType[tag],
180
+ }
181
+ end
182
+ end
183
+ end
184
+ end
data/lib/utils/file.rb CHANGED
@@ -1,18 +1,12 @@
1
1
  module EllipticCurve
2
-
3
2
  module Utils
4
-
5
3
  class File
6
-
7
4
  def self.read(path, encoding="ASCII")
8
5
  file = ::File.open(path, :encoding => encoding.upcase)
9
6
  content = file.read
10
7
  file.close
11
8
  content
12
9
  end
13
-
14
10
  end
15
-
16
11
  end
17
-
18
12
  end
@@ -0,0 +1,23 @@
1
+ require('securerandom')
2
+
3
+ module EllipticCurve
4
+ module Utils
5
+ class RandomInteger
6
+ # Return integer x in the range: min <= x <= max
7
+
8
+ # Parameters (required):
9
+ # :param min: minimum value of the integer
10
+ # :param max: maximum value of the integer
11
+ # :return: A random number between min and max
12
+ def self.between(min, max)
13
+ if (max - min) < 0 then
14
+ raise Exception.new("max must be greater than min")
15
+ end
16
+ if (max - min) > 0 then
17
+ return SecureRandom.random_number((max + 1) - min) + min
18
+ end
19
+ return min
20
+ end
21
+ end
22
+ end
23
+ end
data/lib/utils/oid.rb ADDED
@@ -0,0 +1,43 @@
1
+ module EllipticCurve
2
+ module Utils
3
+ class Oid
4
+ def self.oidFromHex(hexadecimal)
5
+ firstByte, remainingBytes = hexadecimal[0..1], hexadecimal[2..-1]
6
+ firstByteInt = Utils::Binary.intFromHex(firstByte)
7
+ oid = [firstByteInt.div(40), firstByteInt % 40]
8
+ oidInt = 0
9
+ while remainingBytes.to_s.length > 0
10
+ byte, remainingBytes = remainingBytes[0..1], remainingBytes[2..-1]
11
+ byteInt = Utils::Binary.intFromHex(byte)
12
+ if byteInt >= 128
13
+ oidInt = (128 * oidInt) + (byteInt - 128)
14
+ next
15
+ end
16
+ oidInt = (128 * oidInt) + byteInt
17
+ oid.append(oidInt)
18
+ oidInt = 0
19
+ end
20
+ return oid
21
+ end
22
+
23
+ def self.oidToHex(oid)
24
+ hexadecimal = Utils::Binary.hexFromInt(40 * oid[0] + oid[1])
25
+ for number in oid[2..-1]
26
+ hexadecimal += self._oidNumberToHex(number)
27
+ end
28
+ return hexadecimal
29
+ end
30
+
31
+ def self._oidNumberToHex(number)
32
+ hexadecimal = ""
33
+ endDelta = 0
34
+ while number > 0
35
+ hexadecimal = Utils::Binary.hexFromInt((number % 128) + endDelta) + hexadecimal
36
+ number = number.div(128)
37
+ endDelta = 128
38
+ end
39
+ return hexadecimal == "" ? "00" : hexadecimal
40
+ end
41
+ end
42
+ end
43
+ end
data/lib/utils/pem.rb ADDED
@@ -0,0 +1,18 @@
1
+ module EllipticCurve
2
+ module Utils
3
+ class Pem
4
+ def self.getContent(pem, template)
5
+ pattern = template.sub "{content}", ("(.*)")
6
+ return pem.split("\n").join("").match(pattern.split("\n").join("")).captures[0]
7
+ end
8
+
9
+ def self.create(content, template)
10
+ lines = []
11
+ (0..content.length).step(64) do |start|
12
+ lines.append(content[start..start+63])
13
+ end
14
+ return template.sub "{content}", lines.join("\n")
15
+ end
16
+ end
17
+ end
18
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: starkbank-ecdsa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - starkbank
@@ -44,12 +44,20 @@ executables: []
44
44
  extensions: []
45
45
  extra_rdoc_files: []
46
46
  files:
47
+ - lib/curve.rb
47
48
  - lib/ecdsa.rb
49
+ - lib/math.rb
50
+ - lib/point.rb
48
51
  - lib/privatekey.rb
49
52
  - lib/publickey.rb
50
53
  - lib/signature.rb
51
54
  - lib/starkbank-ecdsa.rb
55
+ - lib/utils/binary.rb
56
+ - lib/utils/der.rb
52
57
  - lib/utils/file.rb
58
+ - lib/utils/integer.rb
59
+ - lib/utils/oid.rb
60
+ - lib/utils/pem.rb
53
61
  homepage: https://github.com/starkbank/ecdsa-ruby
54
62
  licenses:
55
63
  - MIT
@@ -62,14 +70,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
62
70
  requirements:
63
71
  - - ">="
64
72
  - !ruby/object:Gem::Version
65
- version: '2.3'
73
+ version: '2.4'
66
74
  required_rubygems_version: !ruby/object:Gem::Requirement
67
75
  requirements:
68
76
  - - ">="
69
77
  - !ruby/object:Gem::Version
70
78
  version: '0'
71
79
  requirements: []
72
- rubygems_version: 3.0.6
80
+ rubygems_version: 3.0.3.1
73
81
  signing_key:
74
82
  specification_version: 4
75
83
  summary: fast openSSL-compatible implementation of the Elliptic Curve Digital Signature