starkbank-ecdsa 2.0.0 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d4a76bc728ec00f073424e79a9e72d75d39aa863074fff03fd5c35694d221f2
4
- data.tar.gz: d8612f17a0eb7b9d7e5d735f7fc164ee288e770ba5d2b4bc62c35fcbda97088e
3
+ metadata.gz: 9b661f1bd378387d7c6466e9271adba268c3e85e7cc3a65ed71bb4e26a6e26d5
4
+ data.tar.gz: 9f815e7c6c4a95f302b12a0458eb6ab24596ff0df5a8cfcb51a608f463ee1b42
5
5
  SHA512:
6
- metadata.gz: 88d83c16ca1e416998cf5411dd45ca4cd9e62af3841dcea5981a2fa8dedbc86ea74a8ef91ceb44db6e5192e36af65c5877f32d373ccffd154c4472951d379fcf
7
- data.tar.gz: 4332d9b28484fe4c09708147494ea33c3caa48f263824a72ef846888a32cd27bad585a98560a8f7883c5be88394da72318e44a144e0b1b4ecfbde148812de058
6
+ metadata.gz: 62071d28e66c906ee70a2a3fe8bc5de2c4174cee49e0d90eb1f19b76014573462ad14035941717f162486dc33780b14e8fbddefff415249d70ba8a16fe79e75a
7
+ data.tar.gz: c1c599e3c8d5bb1f998fe91fdebf54f8b5efd43fb9644fac8425e5f9a20f3dab29b5a430763777b40800e27c7eba106d20c47195caa0ba27520bb5549a2ea3ea
data/lib/curve.rb CHANGED
@@ -6,17 +6,23 @@ module EllipticCurve
6
6
  #
7
7
  module Curve
8
8
  class CurveFp
9
- attr_accessor :a, :b, :p, :n, :g, :name, :oid, :nistName
9
+ attr_accessor :a, :b, :p, :n, :g, :name, :oid, :nistName, :nBitLength, :glvParams
10
+ attr_accessor :_generatorPowersTable
10
11
 
11
- def initialize(a, b, p, n, gx, gy, name, oid, nistName=nil)
12
+ def initialize(a, b, p, n, gx, gy, name, oid, nistName=nil, glvParams=nil)
12
13
  @a = a
13
14
  @b = b
14
15
  @p = p
15
16
  @n = n
17
+ @nBitLength = n.bit_length
16
18
  @g = Point.new(gx, gy)
17
19
  @name = name
18
20
  @oid = oid
19
21
  @nistName = nistName
22
+ # GLV endomorphism parameters (only for curves that support one,
23
+ # e.g. secp256k1). nil means no endomorphism; fall back to Shamir+JSF.
24
+ @glvParams = glvParams
25
+ @_generatorPowersTable = nil
20
26
  end
21
27
 
22
28
  def contains(p)
@@ -71,7 +77,19 @@ module EllipticCurve
71
77
  0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
72
78
  0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8,
73
79
  "secp256k1",
74
- [1, 3, 132, 0, 10]
80
+ [1, 3, 132, 0, 10],
81
+ nil,
82
+ # GLV endomorphism phi((x,y)) = (beta*x, y), equivalent to lambda*P.
83
+ # Basis vectors from Gauss reduction; used to split a 256-bit scalar k
84
+ # into two ~128-bit scalars (k1, k2) with k == k1 + k2*lambda (mod N).
85
+ {
86
+ :beta => 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee,
87
+ :lambda => 0x5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72,
88
+ :a1 => 0x3086d221a7d46bcde86c90e49284eb15,
89
+ :b1 => -0xe4437ed6010e88286f547fa90abfe4c3,
90
+ :a2 => 0x114ca50f7a8e2f3f657c1108d9d44cfd8,
91
+ :b2 => 0x3086d221a7d46bcde86c90e49284eb15,
92
+ }
75
93
  )
76
94
 
77
95
  PRIME256V1 = CurveFp.new(
data/lib/ecdsa.rb CHANGED
@@ -4,15 +4,16 @@ require 'digest'
4
4
  module EllipticCurve
5
5
  module Ecdsa
6
6
  def self.sign(message, privateKey, hashfunc=nil)
7
- if hashfunc.nil? then hashfunc = lambda{ |x| Digest::SHA256.digest(x) } end
8
- byteMessage = hashfunc.call(message)
9
- numberMessage = Utils::Binary.numberFromByteString(byteMessage)
7
+ if hashfunc.nil? then hashfunc = lambda{ |x| Digest::SHA256.digest(x) } end
10
8
  curve = privateKey.curve
9
+ byteMessage = hashfunc.call(message)
10
+ numberMessage = Utils::Binary.numberFromByteString(byteMessage, curve.nBitLength)
11
11
 
12
12
  r, s, randSignPoint = 0, 0, nil
13
+ kIterator = Utils::RandomInteger.rfc6979(byteMessage, privateKey.secret, curve, hashfunc)
13
14
  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)
15
+ randNum = kIterator.next
16
+ randSignPoint = Math.multiplyGenerator(curve, randNum)
16
17
  r = randSignPoint.x % curve.n
17
18
  s = ((numberMessage + r * privateKey.secret) * (Math.inv(randNum, curve.n))) % curve.n
18
19
  end
@@ -20,27 +21,38 @@ module EllipticCurve
20
21
  if randSignPoint.y > curve.n
21
22
  recoveryId += 2
22
23
  end
24
+ if s > curve.n / 2
25
+ s = curve.n - s
26
+ recoveryId ^= 1
27
+ end
28
+
23
29
  return Signature.new(r, s, recoveryId)
24
30
  end
25
31
 
26
32
  def self.verify(message, signature, publicKey, hashfunc=nil)
27
- if hashfunc.nil? then hashfunc = lambda{ |x| Digest::SHA256.digest(x) } end
33
+ if hashfunc.nil? then hashfunc = lambda{ |x| Digest::SHA256.digest(x) } end
34
+ curve = publicKey.curve
28
35
  byteMessage = hashfunc.call(message)
29
- numberMessage = Utils::Binary.numberFromByteString(byteMessage)
36
+ numberMessage = Utils::Binary.numberFromByteString(byteMessage, curve.nBitLength)
30
37
 
31
- curve = publicKey.curve
32
38
  r = signature.r
33
39
  s = signature.s
40
+
34
41
  if not (1 <= r and r <= curve.n - 1)
35
42
  return false
36
43
  end
37
44
  if not (1 <= s and s <= curve.n - 1)
38
45
  return false
39
46
  end
47
+ if not curve.contains(publicKey.point)
48
+ return false
49
+ end
40
50
  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)
51
+ v = Math.multiplyAndAdd(
52
+ curve.g, (numberMessage * inv) % curve.n,
53
+ publicKey.point, (r * inv) % curve.n,
54
+ curve.n, curve.a, curve.p, curve,
55
+ )
44
56
  if v.isAtInfinity
45
57
  return false
46
58
  end
data/lib/math.rb CHANGED
@@ -1,21 +1,129 @@
1
1
  module EllipticCurve
2
2
  class Math
3
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)
4
+ # Tonelli-Shanks algorithm for modular square root. Works for all odd primes.
5
+ if value == 0
6
+ return 0
7
+ end
8
+ if prime == 2
9
+ return value % 2
10
+ end
11
+
12
+ # Factor out powers of 2: prime - 1 = Q * 2^S
13
+ q = prime - 1
14
+ s = 0
15
+ while q % 2 == 0
16
+ q /= 2
17
+ s += 1
18
+ end
19
+
20
+ if s == 1 # prime = 3 (mod 4)
21
+ return value.pow((prime + 1) / 4, prime)
22
+ end
23
+
24
+ # Find a quadratic non-residue z
25
+ z = 2
26
+ while z.pow((prime - 1) / 2, prime) != prime - 1
27
+ z += 1
28
+ end
29
+
30
+ m = s
31
+ c = z.pow(q, prime)
32
+ t = value.pow(q, prime)
33
+ r = value.pow((q + 1) / 2, prime)
34
+
35
+ while true
36
+ if t == 1
37
+ return r
38
+ end
39
+
40
+ # Find the least i such that t^(2^i) = 1 (mod prime)
41
+ i = 1
42
+ temp = (t * t) % prime
43
+ while temp != 1
44
+ temp = (temp * temp) % prime
45
+ i += 1
46
+ end
47
+
48
+ b = c.pow(1 << (m - i - 1), prime)
49
+ m = i
50
+ c = (b * b) % prime
51
+ t = (t * c) % prime
52
+ r = (r * b) % prime
53
+ end
8
54
  end
9
55
 
10
- def self.multiply(p, n, order, coeff, prime)
11
- # Fast way to multiply point and scalar in elliptic curves
56
+ def self.multiplyGenerator(curve, n)
57
+ # Fast scalar multiplication n*G using a precomputed affine table of
58
+ # powers-of-two multiples of G and the width-2 NAF of n. Every non-zero
59
+ # NAF digit triggers one mixed add and zero doublings, trading the ~256
60
+ # doublings of a windowed method for ~86 adds on average - a large net
61
+ # reduction in field multiplications for 256-bit scalars.
62
+ if n < 0 or n >= curve.n
63
+ n = n % curve.n
64
+ end
65
+ if n == 0
66
+ return Point.new(0, 0, 0)
67
+ end
12
68
 
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
69
+ table = self._generatorPowersTable(curve)
70
+ coeff = curve.a
71
+ prime = curve.p
72
+
73
+ r = Point.new(0, 0, 1)
74
+ i = 0
75
+ k = n
76
+ while k > 0
77
+ if (k & 1) != 0
78
+ digit = 2 - (k & 3) # -1 or +1
79
+ k -= digit
80
+ g = table[i]
81
+ if digit == 1
82
+ r = self._jacobianAdd(r, g, coeff, prime)
83
+ else
84
+ r = self._jacobianAdd(r, Point.new(g.x, prime - g.y, 1), coeff, prime)
85
+ end
86
+ end
87
+ k >>= 1
88
+ i += 1
89
+ end
90
+ return self._fromJacobian(r, prime)
91
+ end
92
+
93
+ def self._generatorPowersTable(curve)
94
+ # Build [G, 2G, 4G, ..., 2^nBitLength * G] in affine (z=1) form, so each
95
+ # add in multiplyGenerator hits the mixed-add fast path.
96
+ return curve._generatorPowersTable unless curve._generatorPowersTable.nil?
97
+ coeff = curve.a
98
+ prime = curve.p
99
+ current = Point.new(curve.g.x, curve.g.y, 1)
100
+ table = [current]
101
+ # NAF of an nBitLength-bit scalar can be up to nBitLength+1 digits.
102
+ curve.nBitLength.times do
103
+ doubled = self._jacobianDouble(current, coeff, prime)
104
+ if doubled.y == 0
105
+ current = doubled
106
+ else
107
+ zInv = self.inv(doubled.z, prime)
108
+ zInv2 = (zInv * zInv) % prime
109
+ zInv3 = (zInv2 * zInv) % prime
110
+ current = Point.new((doubled.x * zInv2) % prime, (doubled.y * zInv3) % prime, 1)
111
+ end
112
+ table << current
113
+ end
114
+ curve._generatorPowersTable = table
115
+ return table
116
+ end
117
+
118
+ def self.multiply(p, n, order, coeff, prime)
119
+ # Fast way to multiply point and scalar in elliptic curves
120
+ #
121
+ # :param p: First Point to multiply
122
+ # :param n: Scalar to multiply
123
+ # :param order: Order of the elliptic curve
124
+ # :param coeff: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
125
+ # :param prime: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
126
+ # :return: Point that represents the scalar multiplication
19
127
  return self._fromJacobian(
20
128
  self._jacobianMultiply(self._toJacobian(p), n, order, coeff, prime), prime
21
129
  )
@@ -23,45 +131,138 @@ module EllipticCurve
23
131
 
24
132
  def self.add(p, q, coeff, prime)
25
133
  # Fast way to add two points in elliptic curves
26
-
134
+ #
27
135
  # :param p: First Point you want to add
28
136
  # :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)
137
+ # :param coeff: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
138
+ # :param prime: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
31
139
  # :return: Point that represents the sum of First and Second Point
32
140
  return self._fromJacobian(
33
141
  self._jacobianAdd(self._toJacobian(p), self._toJacobian(q), coeff, prime), prime
34
142
  )
35
143
  end
36
144
 
37
- def self.inv(x, n)
38
- # Extended Euclidean Algorithm. It's the 'division' in elliptic curves
145
+ def self.multiplyAndAdd(p1, n1, p2, n2, order, coeff, prime, curve=nil)
146
+ # Compute n1*p1 + n2*p2. If `curve` is given and exposes `glvParams`
147
+ # (e.g. secp256k1), uses the GLV endomorphism to split both scalars
148
+ # into ~128-bit halves and run a 4-scalar simultaneous multi-
149
+ # exponentiation. Otherwise falls back to Shamir's trick with JSF.
150
+ # Not constant-time - use only with public scalars (e.g. verification).
151
+ #
152
+ # :param p1: First point
153
+ # :param n1: First scalar
154
+ # :param p2: Second point
155
+ # :param n2: Second scalar
156
+ # :param order: Order of the elliptic curve (ignored when `curve` is given)
157
+ # :param coeff: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p) (ignored when `curve` is given)
158
+ # :param prime: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p) (ignored when `curve` is given)
159
+ # :param curve: Optional curve object; enables GLV if `curve.glvParams` is set
160
+ # :return: Point n1*p1 + n2*p2
161
+ if not curve.nil?
162
+ order, coeff, prime = curve.n, curve.a, curve.p
163
+ if not curve.glvParams.nil?
164
+ return self._glvMultiplyAndAdd(p1, n1, p2, n2, curve)
165
+ end
166
+ end
167
+ return self._fromJacobian(
168
+ self._shamirMultiply(
169
+ self._toJacobian(p1), n1,
170
+ self._toJacobian(p2), n2,
171
+ order, coeff, prime,
172
+ ), prime,
173
+ )
174
+ end
175
+
176
+ def self._glvMultiplyAndAdd(p1, n1, p2, n2, curve)
177
+ # Compute n1*p1 + n2*p2 using the GLV endomorphism. Splits each 256-bit
178
+ # scalar into two ~128-bit scalars via k == k1 + k2*lambda (mod N), then
179
+ # runs a 4-scalar simultaneous double-and-add over (p1, phi(p1), p2,
180
+ # phi(p2)) with a 16-entry precomputed table of subset sums. Halves the
181
+ # loop length versus the plain Shamir path.
182
+ glv = curve.glvParams
183
+ order, coeff, prime = curve.n, curve.a, curve.p
184
+ beta = glv[:beta]
185
+
186
+ k1, k2 = self._glvDecompose(n1 % order, glv, order)
187
+ k3, k4 = self._glvDecompose(n2 % order, glv, order)
188
+
189
+ # Base points (affine, z=1) - phi((x,y)) = (beta*x mod P, y).
190
+ bases = [
191
+ Point.new(p1.x, p1.y, 1),
192
+ Point.new((beta * p1.x) % prime, p1.y, 1),
193
+ Point.new(p2.x, p2.y, 1),
194
+ Point.new((beta * p2.x) % prime, p2.y, 1),
195
+ ]
196
+ scalars = [k1, k2, k3, k4]
197
+ (0...4).each do |i|
198
+ if scalars[i] < 0
199
+ scalars[i] = -scalars[i]
200
+ bases[i] = Point.new(bases[i].x, prime - bases[i].y, 1)
201
+ end
202
+ end
203
+
204
+ # Precompute table[idx] = sum of bases[i] selected by bits of idx.
205
+ table = Array.new(16, Point.new(0, 0, 1))
206
+ (1...16).each do |idx|
207
+ low = idx & -idx
208
+ i = low.bit_length - 1
209
+ table[idx] = self._jacobianAdd(table[idx ^ low], bases[i], coeff, prime)
210
+ end
211
+
212
+ maxLen = scalars.map(&:bit_length).max
213
+ r = Point.new(0, 0, 1)
214
+ s0, s1, s2, s3 = scalars
215
+ (maxLen - 1).downto(0) do |bit|
216
+ r = self._jacobianDouble(r, coeff, prime)
217
+ idx = ((s0 >> bit) & 1) | (((s1 >> bit) & 1) << 1) \
218
+ | (((s2 >> bit) & 1) << 2) | (((s3 >> bit) & 1) << 3)
219
+ if idx != 0
220
+ r = self._jacobianAdd(r, table[idx], coeff, prime)
221
+ end
222
+ end
223
+
224
+ return self._fromJacobian(r, prime)
225
+ end
226
+
227
+ def self._glvDecompose(k, glv, order)
228
+ # Decompose k into (k1, k2) with k == k1 + k2*lambda (mod N) and
229
+ # |k1|, |k2| ~ sqrt(N). Babai rounding against the precomputed basis
230
+ # {(a1, b1), (a2, b2)}; k1 and k2 may be negative.
231
+ a1, b1, a2, b2 = glv[:a1], glv[:b1], glv[:a2], glv[:b2]
232
+ halfN = order / 2
233
+ c1 = (b2 * k + halfN) / order
234
+ c2 = (-b1 * k + halfN) / order
235
+ k1 = k - c1 * a1 - c2 * a2
236
+ k2 = -c1 * b1 - c2 * b2
237
+ return k1, k2
238
+ end
39
239
 
40
- # :param x: Divisor
240
+ def self.inv(x, n)
241
+ # Modular inverse via extended Euclidean algorithm.
242
+ # Roughly 2-3x faster than Fermat's little theorem for 256-bit operands.
243
+ #
244
+ # :param x: Divisor (must be coprime to n)
41
245
  # :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
246
+ # :return: Value representing the modular inverse
247
+ if x % n == 0
248
+ raise ArgumentError, "0 has no modular inverse"
249
+ end
250
+
251
+ a = x % n
252
+ b = n
253
+ x0 = 1
254
+ x1 = 0
255
+ while b != 0
256
+ q = a / b
257
+ a, b = b, a - q * b
258
+ x0, x1 = x1, x0 - q * x1
259
+ end
260
+ return x0 % n
60
261
  end
61
262
 
62
263
  def self._toJacobian(p)
63
264
  # Convert point to Jacobian coordinates
64
-
265
+ #
65
266
  # :param p: First Point you want to add
66
267
  # :return: Point in Jacobian coordinates
67
268
  return Point.new(p.x, p.y, 1)
@@ -69,10 +270,14 @@ module EllipticCurve
69
270
 
70
271
  def self._fromJacobian(p, prime)
71
272
  # Convert point back from Jacobian coordinates
72
-
273
+ #
73
274
  # :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)
275
+ # :param prime: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
75
276
  # :return: Point in default coordinates
277
+ if p.y == 0
278
+ return Point.new(0, 0, 0)
279
+ end
280
+
76
281
  z = self.inv(p.z, prime)
77
282
  x = (p.x * z ** 2) % prime
78
283
  y = (p.y * z ** 3) % prime
@@ -82,41 +287,70 @@ module EllipticCurve
82
287
 
83
288
  def self._jacobianDouble(p, coeff, prime)
84
289
  # Double a point in elliptic curves
85
-
290
+ #
86
291
  # :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)
292
+ # :param coeff: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
293
+ # :param prime: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
89
294
  # :return: Point that represents the sum of First and Second Point
90
- if p.y == 0 then return Point.new(0, 0, 0) end
295
+ py = p.y
296
+ if py == 0
297
+ return Point.new(0, 0, 0)
298
+ end
91
299
 
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
300
+ px, pz = p.x, p.z
301
+ ysq = (py * py) % prime
302
+ s = (4 * px * ysq) % prime
303
+ pz2 = (pz * pz) % prime
304
+ if coeff == 0
305
+ m = (3 * px * px) % prime
306
+ elsif coeff == prime - 3
307
+ m = (3 * (px - pz2) * (px + pz2)) % prime
308
+ else
309
+ m = (3 * px * px + coeff * pz2 * pz2) % prime
310
+ end
311
+ nx = (m * m - 2 * s) % prime
312
+ ny = (m * (s - nx) - 8 * ysq * ysq) % prime
313
+ nz = (2 * py * pz) % prime
98
314
 
99
315
  return Point.new(nx, ny, nz)
100
316
  end
101
317
 
102
318
  def self._jacobianAdd(p, q, coeff, prime)
103
319
  # Add two points in elliptic curves
104
-
320
+ #
105
321
  # :param p: First Point you want to add
106
322
  # :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)
323
+ # :param coeff: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
324
+ # :param prime: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
109
325
  # :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
326
+ if p.y == 0
327
+ return q
328
+ end
329
+ if q.y == 0
330
+ return p
331
+ end
112
332
 
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
333
+ px, py, pz = p.x, p.y, p.z
334
+ qx, qy, qz = q.x, q.y, q.z
335
+
336
+ pz2 = (pz * pz) % prime
337
+ u2 = (qx * pz2) % prime
338
+ s2 = (qy * pz2 * pz) % prime
339
+
340
+ if qz == 1
341
+ # Mixed affine+Jacobian add: qz^2=qz^3=1 saves four multiplications.
342
+ u1 = px
343
+ s1 = py
344
+ else
345
+ qz2 = (qz * qz) % prime
346
+ u1 = (px * qz2) % prime
347
+ s1 = (py * qz2 * qz) % prime
348
+ end
117
349
 
118
350
  if u1 == u2
119
- if s1 != s2 then return Point.new(0, 0, 1) end
351
+ if s1 != s2
352
+ return Point.new(0, 0, 1)
353
+ end
120
354
  return self._jacobianDouble(p, coeff, prime)
121
355
  end
122
356
 
@@ -125,43 +359,141 @@ module EllipticCurve
125
359
  h2 = (h * h) % prime
126
360
  h3 = (h * h2) % prime
127
361
  u1h2 = (u1 * h2) % prime
128
- nx = (r ** 2 - h3 - 2 * u1h2) % prime
362
+ nx = (r * r - h3 - 2 * u1h2) % prime
129
363
  ny = (r * (u1h2 - nx) - s1 * h3) % prime
130
- nz = (h * p.z * q.z) % prime
364
+ nz = qz == 1 ? (h * pz) % prime : (h * pz * qz) % prime
131
365
 
132
366
  return Point.new(nx, ny, nz)
133
367
  end
134
368
 
135
369
  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
370
+ # Multiply point and scalar in elliptic curves using Montgomery ladder
371
+ # for constant-time execution.
372
+ #
373
+ # :param p: First Point to multiply
374
+ # :param n: Scalar to multiply
375
+ # :param order: Order of the elliptic curve
376
+ # :param coeff: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
377
+ # :param prime: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
378
+ # :return: Point that represents the scalar multiplication
144
379
  if p.y == 0 or n == 0
145
380
  return Point.new(0, 0, 1)
146
381
  end
147
382
 
148
- if n == 1
149
- return p
383
+ if n < 0 or n >= order
384
+ n = n % order
150
385
  end
151
386
 
152
- if n < 0 or n >= order
153
- return self._jacobianMultiply(p, n % order, order, coeff, prime)
387
+ if n == 0
388
+ return Point.new(0, 0, 1)
154
389
  end
155
390
 
156
- if (n % 2) == 0
157
- return self._jacobianDouble(
158
- self._jacobianMultiply(p, n.div(2), order, coeff, prime), coeff, prime
159
- )
391
+ # Montgomery ladder: always performs one add and one double per bit
392
+ r0 = Point.new(0, 0, 1)
393
+ r1 = Point.new(p.x, p.y, p.z)
394
+
395
+ (n.bit_length - 1).downto(0) do |i|
396
+ if (n >> i) & 1 == 0
397
+ r1 = self._jacobianAdd(r0, r1, coeff, prime)
398
+ r0 = self._jacobianDouble(r0, coeff, prime)
399
+ else
400
+ r0 = self._jacobianAdd(r0, r1, coeff, prime)
401
+ r1 = self._jacobianDouble(r1, coeff, prime)
402
+ end
160
403
  end
161
404
 
162
- return self._jacobianAdd(
163
- self._jacobianDouble(self._jacobianMultiply(p, n.div(2), order, coeff, prime), coeff, prime), p, coeff, prime
164
- )
405
+ return r0
406
+ end
407
+
408
+ def self._shamirMultiply(jp1, n1, jp2, n2, order, coeff, prime)
409
+ # Compute n1*p1 + n2*p2 using Shamir's trick with Joint Sparse Form
410
+ # (Solinas 2001). JSF picks signed digits in {-1, 0, 1} so at most ~l/2
411
+ # digit pairs are non-zero, versus ~3l/4 for the raw binary form. Not
412
+ # constant-time - use only with public scalars (e.g. verification).
413
+ #
414
+ # :param jp1: First point in Jacobian coordinates
415
+ # :param n1: First scalar
416
+ # :param jp2: Second point in Jacobian coordinates
417
+ # :param n2: Second scalar
418
+ # :param order: Order of the elliptic curve
419
+ # :param coeff: Coefficient of the first-order term of the equation Y^2 = X^3 + A*X + B (mod p)
420
+ # :param prime: Prime number in the module of the equation Y^2 = X^3 + A*X + B (mod p)
421
+ # :return: Point n1*p1 + n2*p2 in Jacobian coordinates
422
+ if n1 < 0 or n1 >= order
423
+ n1 = n1 % order
424
+ end
425
+ if n2 < 0 or n2 >= order
426
+ n2 = n2 % order
427
+ end
428
+
429
+ if n1 == 0 and n2 == 0
430
+ return Point.new(0, 0, 1)
431
+ end
432
+
433
+ neg = lambda { |pt| Point.new(pt.x, pt.y == 0 ? 0 : prime - pt.y, pt.z) }
434
+
435
+ jp1p2 = self._jacobianAdd(jp1, jp2, coeff, prime)
436
+ jp1mp2 = self._jacobianAdd(jp1, neg.call(jp2), coeff, prime)
437
+ addTable = {
438
+ [1, 0] => jp1,
439
+ [-1, 0] => neg.call(jp1),
440
+ [0, 1] => jp2,
441
+ [0, -1] => neg.call(jp2),
442
+ [1, 1] => jp1p2,
443
+ [-1, -1] => neg.call(jp1p2),
444
+ [1, -1] => jp1mp2,
445
+ [-1, 1] => neg.call(jp1mp2),
446
+ }
447
+
448
+ digits = self._jsfDigits(n1, n2)
449
+ r = Point.new(0, 0, 1)
450
+ digits.each do |u0, u1|
451
+ r = self._jacobianDouble(r, coeff, prime)
452
+ if u0 != 0 or u1 != 0
453
+ r = self._jacobianAdd(r, addTable[[u0, u1]], coeff, prime)
454
+ end
455
+ end
456
+
457
+ return r
458
+ end
459
+
460
+ def self._jsfDigits(k0, k1)
461
+ # Joint Sparse Form of (k0, k1): list of signed-digit pairs (u0, u1) in
462
+ # {-1, 0, 1}, ordered MSB-first. At most one of any two consecutive pairs
463
+ # is non-zero, giving density ~1/2 instead of ~3/4 from raw binary.
464
+ digits = []
465
+ d0 = 0
466
+ d1 = 0
467
+ while k0 + d0 != 0 or k1 + d1 != 0
468
+ a0 = k0 + d0
469
+ a1 = k1 + d1
470
+ if (a0 & 1) != 0
471
+ u0 = (a0 & 3) == 1 ? 1 : -1
472
+ if [3, 5].include?(a0 & 7) and (a1 & 3) == 2
473
+ u0 = -u0
474
+ end
475
+ else
476
+ u0 = 0
477
+ end
478
+ if (a1 & 1) != 0
479
+ u1 = (a1 & 3) == 1 ? 1 : -1
480
+ if [3, 5].include?(a1 & 7) and (a0 & 3) == 2
481
+ u1 = -u1
482
+ end
483
+ else
484
+ u1 = 0
485
+ end
486
+ digits << [u0, u1]
487
+ if 2 * d0 == 1 + u0
488
+ d0 = 1 - d0
489
+ end
490
+ if 2 * d1 == 1 + u1
491
+ d1 = 1 - d1
492
+ end
493
+ k0 >>= 1
494
+ k1 >>= 1
495
+ end
496
+ digits.reverse
165
497
  end
166
498
  end
167
499
  end
data/lib/utils/binary.rb CHANGED
@@ -24,8 +24,15 @@ module EllipticCurve
24
24
  return [hexadecimal].pack("H*")
25
25
  end
26
26
 
27
- def self.numberFromByteString(bytes)
28
- return bytes.unpack("C*").reduce(0) { |number, byte| number * 256 + byte }
27
+ def self.numberFromByteString(bytes, bitLength=nil)
28
+ number = bytes.unpack("C*").reduce(0) { |n, byte| n * 256 + byte }
29
+ if !bitLength.nil?
30
+ hashBitLen = bytes.bytesize * 8
31
+ if hashBitLen > bitLength
32
+ number >>= (hashBitLen - bitLength)
33
+ end
34
+ end
35
+ return number
29
36
  end
30
37
 
31
38
  def self.base64FromByteString(byteString)
data/lib/utils/der.rb CHANGED
@@ -140,7 +140,7 @@ module EllipticCurve
140
140
  raise Exception.new("indefinite length encoding located in DER")
141
141
  end
142
142
  lengthBytes += 2 * lengthLength
143
- length = Utils::Binary.intFromHex(hexadecimal[2, lengthBytes]) * 2
143
+ length = Utils::Binary.intFromHex(hexadecimal[2...lengthBytes]) * 2
144
144
  return length, lengthBytes
145
145
  end
146
146
 
data/lib/utils/integer.rb CHANGED
@@ -1,13 +1,14 @@
1
1
  require('securerandom')
2
+ require('openssl')
2
3
 
3
4
  module EllipticCurve
4
5
  module Utils
5
6
  class RandomInteger
6
7
  # Return integer x in the range: min <= x <= max
7
-
8
+ #
8
9
  # Parameters (required):
9
10
  # :param min: minimum value of the integer
10
- # :param max: maximum value of the integer
11
+ # :param max: maximum value of the integer
11
12
  # :return: A random number between min and max
12
13
  def self.between(min, max)
13
14
  if (max - min) < 0 then
@@ -18,6 +19,71 @@ module EllipticCurve
18
19
  end
19
20
  return min
20
21
  end
22
+
23
+ def self.rfc6979(hashBytes, secret, curve, hashfunc)
24
+ # Generate nonce values per hedged RFC 6979: deterministic k derivation
25
+ # with fresh random entropy mixed into K-init (RFC 6979 §3.6). Same message
26
+ # and key yield different signatures, while preserving RFC 6979's protection
27
+ # against RNG failures.
28
+ orderBitLen = curve.nBitLength
29
+ orderByteLen = (orderBitLen + 7) / 8
30
+
31
+ secretHex = Binary.hexFromInt(secret).rjust(orderByteLen * 2, "0")
32
+ secretBytes = Binary.byteStringFromHex(secretHex)
33
+
34
+ hashReduced = Binary.numberFromByteString(hashBytes, orderBitLen) % curve.n
35
+ hashHex = Binary.hexFromInt(hashReduced).rjust(orderByteLen * 2, "0")
36
+ hashOctets = Binary.byteStringFromHex(hashHex)
37
+
38
+ extraEntropyHex = Binary.hexFromInt(between(0, (1 << (orderByteLen * 8)) - 1)).rjust(orderByteLen * 2, "0")
39
+ extraEntropy = Binary.byteStringFromHex(extraEntropyHex)
40
+
41
+ hLen = hashfunc.call("").bytesize
42
+ digestName = _digestNameFromLength(hLen)
43
+ v = "\x01".b * hLen
44
+ k = "\x00".b * hLen
45
+
46
+ k = OpenSSL::HMAC.digest(digestName, k, v + "\x00".b + secretBytes + hashOctets + extraEntropy)
47
+ v = OpenSSL::HMAC.digest(digestName, k, v)
48
+ k = OpenSSL::HMAC.digest(digestName, k, v + "\x01".b + secretBytes + hashOctets + extraEntropy)
49
+ v = OpenSSL::HMAC.digest(digestName, k, v)
50
+
51
+ Enumerator.new do |yielder|
52
+ loop do
53
+ t = "".b
54
+ while t.bytesize * 8 < orderBitLen
55
+ v = OpenSSL::HMAC.digest(digestName, k, v)
56
+ t += v
57
+ end
58
+
59
+ kCandidate = Binary.numberFromByteString(t, orderBitLen)
60
+
61
+ if kCandidate >= 1 && kCandidate <= curve.n - 1
62
+ yielder.yield kCandidate
63
+ end
64
+
65
+ k = OpenSSL::HMAC.digest(digestName, k, v + "\x00".b)
66
+ v = OpenSSL::HMAC.digest(digestName, k, v)
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def self._digestNameFromLength(hLen)
74
+ case hLen
75
+ when 32
76
+ "SHA256"
77
+ when 48
78
+ "SHA384"
79
+ when 64
80
+ "SHA512"
81
+ when 20
82
+ "SHA1"
83
+ else
84
+ "SHA256"
85
+ end
86
+ end
21
87
  end
22
88
  end
23
89
  end
metadata CHANGED
@@ -1,11 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: starkbank-ecdsa
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - starkbank
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
10
  date: 2019-11-21 00:00:00.000000000 Z
@@ -38,8 +37,6 @@ dependencies:
38
37
  - - "~>"
39
38
  - !ruby/object:Gem::Version
40
39
  version: 5.14.1
41
- description:
42
- email:
43
40
  executables: []
44
41
  extensions: []
45
42
  extra_rdoc_files: []
@@ -62,7 +59,6 @@ homepage: https://github.com/starkbank/ecdsa-ruby
62
59
  licenses:
63
60
  - MIT
64
61
  metadata: {}
65
- post_install_message:
66
62
  rdoc_options: []
67
63
  require_paths:
68
64
  - lib
@@ -77,8 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
77
73
  - !ruby/object:Gem::Version
78
74
  version: '0'
79
75
  requirements: []
80
- rubygems_version: 3.0.3.1
81
- signing_key:
76
+ rubygems_version: 4.0.6
82
77
  specification_version: 4
83
78
  summary: fast openSSL-compatible implementation of the Elliptic Curve Digital Signature
84
79
  Algorithm (ECDSA)