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 +4 -4
- data/lib/curve.rb +21 -3
- data/lib/ecdsa.rb +23 -11
- data/lib/math.rb +412 -80
- data/lib/utils/binary.rb +9 -2
- data/lib/utils/der.rb +1 -1
- data/lib/utils/integer.rb +68 -2
- metadata +2 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b661f1bd378387d7c6466e9271adba268c3e85e7cc3a65ed71bb4e26a6e26d5
|
|
4
|
+
data.tar.gz: 9f815e7c6c4a95f302b12a0458eb6ab24596ff0df5a8cfcb51a608f463ee1b42
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 =
|
|
15
|
-
randSignPoint = Math.
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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.
|
|
11
|
-
# Fast
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
30
|
-
# :param
|
|
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.
|
|
38
|
-
#
|
|
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
|
-
|
|
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
|
|
43
|
-
if x
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
|
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
|
|
88
|
-
# :param
|
|
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
|
-
|
|
295
|
+
py = p.y
|
|
296
|
+
if py == 0
|
|
297
|
+
return Point.new(0, 0, 0)
|
|
298
|
+
end
|
|
91
299
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
108
|
-
# :param
|
|
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
|
|
111
|
-
|
|
326
|
+
if p.y == 0
|
|
327
|
+
return q
|
|
328
|
+
end
|
|
329
|
+
if q.y == 0
|
|
330
|
+
return p
|
|
331
|
+
end
|
|
112
332
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
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
|
|
362
|
+
nx = (r * r - h3 - 2 * u1h2) % prime
|
|
129
363
|
ny = (r * (u1h2 - nx) - s1 * h3) % prime
|
|
130
|
-
nz = (h *
|
|
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
|
-
#
|
|
139
|
-
# :param
|
|
140
|
-
# :param
|
|
141
|
-
# :param
|
|
142
|
-
# :param
|
|
143
|
-
# :
|
|
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
|
|
149
|
-
|
|
383
|
+
if n < 0 or n >= order
|
|
384
|
+
n = n % order
|
|
150
385
|
end
|
|
151
386
|
|
|
152
|
-
if n
|
|
153
|
-
return
|
|
387
|
+
if n == 0
|
|
388
|
+
return Point.new(0, 0, 1)
|
|
154
389
|
end
|
|
155
390
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
163
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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)
|