starkbank-ecdsa 0.0.5 → 2.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
  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