rb-pure25519 1.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 +7 -0
- data/LICENSE +32 -0
- data/lib/rb-pure25519.rb +803 -0
- metadata +45 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c86f97c8304808ea193c621388ac448c5feb7d3c
|
4
|
+
data.tar.gz: c07984a8e9c391b15467f3eba9de80881cd4ed5d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 25ad334cedb874dd433a051bc4d6cfc2a33150686def8ca17d928cccaebb639afb14d44bae4f5e0e2fc51f1da6d90ff9fe065520fae313724c7db124e01bc87a
|
7
|
+
data.tar.gz: 391e53f6744fa9f1b29fbe4870bbf320325e42304a820d71092ae9e2a9ef0025f660a94526faf966c4cce5b6598336c0ed661d2621047d7e05a9f1f93a3d1b74
|
data/LICENSE
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Daniel Pruessner
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
23
|
+
|
24
|
+
I really cannot stress enough how, if you want to use this library, you should
|
25
|
+
do your own damn homework and make sure it is secure enough for your needs. I
|
26
|
+
took pains to make sure the tests work; however, it may not be immune to
|
27
|
+
side-channel attacks related to how Ruby implements the multiplications in
|
28
|
+
Bignum or other places. However, if your attacker can measure the power of the
|
29
|
+
CPU on a modern CPU that can run Ruby, then they probably have **far** easier
|
30
|
+
ways of reading memory across data busses to gain access to secure keys. Or
|
31
|
+
access to root accounts and they can just read the main memory. It's really
|
32
|
+
easy to find EC25519 keys in memory. — Daniel
|
data/lib/rb-pure25519.rb
ADDED
@@ -0,0 +1,803 @@
|
|
1
|
+
#
|
2
|
+
# Finite field algebra
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'prime'
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
class Integer
|
11
|
+
def ferm_is_prime?
|
12
|
+
if self.bit_length < 10
|
13
|
+
return Prime.first(200).member? self
|
14
|
+
end
|
15
|
+
Rb25519::FField.rosetta_mod_exp(2, self-1, self) == 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def ferm_ndiv(v)
|
19
|
+
a = self / v
|
20
|
+
b = self - (a * v)
|
21
|
+
[a,b]
|
22
|
+
end
|
23
|
+
|
24
|
+
def rb25519_clamp
|
25
|
+
v = self & 248
|
26
|
+
v &= (127 << (31*8))
|
27
|
+
v |= ( 64 << (31*8))
|
28
|
+
v
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_binary_string
|
32
|
+
v = self
|
33
|
+
ary = []
|
34
|
+
while v > 0
|
35
|
+
ary << (v & 0xFF)
|
36
|
+
v >>= 8
|
37
|
+
end
|
38
|
+
ary.pack('c*')
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
class String
|
44
|
+
#
|
45
|
+
# Convert to a binary fixed size; LSB first
|
46
|
+
def to_binary
|
47
|
+
v = 0
|
48
|
+
self.reverse.each_byte do |byte|
|
49
|
+
v = (v << 8) | byte
|
50
|
+
end
|
51
|
+
v
|
52
|
+
end
|
53
|
+
def rb25519_clamp
|
54
|
+
bytes = self.each_byte.to_a
|
55
|
+
bytes[0] &= 248;
|
56
|
+
bytes[31] &= 127;
|
57
|
+
bytes[31] |= 64;
|
58
|
+
|
59
|
+
return bytes.pack('c*')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Array
|
64
|
+
def to_xz
|
65
|
+
[ self[0], self[0].f[1] ]
|
66
|
+
end
|
67
|
+
def to_xy
|
68
|
+
self[0] / self[1]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
class ECInfinity
|
76
|
+
def self.to_xy
|
77
|
+
self
|
78
|
+
end
|
79
|
+
def inspect
|
80
|
+
"#<ECInfinity>"
|
81
|
+
end
|
82
|
+
def to_s; inspect; end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
module Rb25519
|
88
|
+
class FField
|
89
|
+
class FFieldValue
|
90
|
+
attr_accessor :val
|
91
|
+
|
92
|
+
def ==(v)
|
93
|
+
@val == v.to_i
|
94
|
+
end
|
95
|
+
def f
|
96
|
+
@field
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_i
|
100
|
+
@val
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_f
|
104
|
+
@val.to_f
|
105
|
+
end
|
106
|
+
|
107
|
+
def initialize(f, v)
|
108
|
+
@field = f
|
109
|
+
@val = v.to_i
|
110
|
+
|
111
|
+
if @val >= @field.p || @val < 0
|
112
|
+
@val = @val % @field.p
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def +(v)
|
117
|
+
FFieldValue.new(@field, @field.add(self, v))
|
118
|
+
end
|
119
|
+
|
120
|
+
def -(v)
|
121
|
+
FFieldValue.new(@field, @field.sub(self, v))
|
122
|
+
end
|
123
|
+
|
124
|
+
def *(v)
|
125
|
+
FFieldValue.new(@field, @field.mul(self, v))
|
126
|
+
end
|
127
|
+
|
128
|
+
def inv
|
129
|
+
FFieldValue.new(@field, @field.inv(self))
|
130
|
+
end
|
131
|
+
|
132
|
+
def /(v)
|
133
|
+
FFieldValue.new(@field, @field.div(self, v))
|
134
|
+
end
|
135
|
+
|
136
|
+
def **(v)
|
137
|
+
FFieldValue.new(@field, @field.exp(self, v))
|
138
|
+
end
|
139
|
+
|
140
|
+
def sqrt
|
141
|
+
@field.sqrt(self).map{|e| FFieldValue.new(@field, e) }
|
142
|
+
end
|
143
|
+
|
144
|
+
def -@()
|
145
|
+
self * -1
|
146
|
+
end
|
147
|
+
|
148
|
+
def inspect
|
149
|
+
"#<FFieldValue_#{@field.p}: #{@val} >"
|
150
|
+
end
|
151
|
+
|
152
|
+
def to_s
|
153
|
+
inspect
|
154
|
+
end
|
155
|
+
|
156
|
+
end # FFieldValue
|
157
|
+
|
158
|
+
|
159
|
+
|
160
|
+
|
161
|
+
#
|
162
|
+
# Class methods
|
163
|
+
#
|
164
|
+
def self.rosetta_mod_exp(b, exp, mod)
|
165
|
+
exp < 0 and raise ArgumentError, "negative exponent"
|
166
|
+
prod = 1
|
167
|
+
base = b % mod
|
168
|
+
until exp.zero?
|
169
|
+
exp.odd? and prod = (prod * base) % mod
|
170
|
+
exp >>= 1
|
171
|
+
base = (base * base) % mod
|
172
|
+
end
|
173
|
+
prod
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.eea(i,j)
|
177
|
+
s,t,u,v = 1,0,0,1
|
178
|
+
while (j != 0)
|
179
|
+
q, r = i / j, i % j
|
180
|
+
unew, vnew = s , t
|
181
|
+
|
182
|
+
s = u - (q * s)
|
183
|
+
t = v - (q * t)
|
184
|
+
|
185
|
+
i, j = j, r
|
186
|
+
u, v = unew, vnew
|
187
|
+
|
188
|
+
|
189
|
+
end
|
190
|
+
d, m, n = i, u, v
|
191
|
+
|
192
|
+
return [d, m, n]
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
|
197
|
+
|
198
|
+
attr_accessor :p
|
199
|
+
|
200
|
+
def initialize(size)
|
201
|
+
raise RuntimeError.new("Field must be prime") unless size.ferm_is_prime?
|
202
|
+
@p = size
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
def [](v)
|
207
|
+
FFieldValue.new(self, v)
|
208
|
+
end
|
209
|
+
|
210
|
+
def add(a,b)
|
211
|
+
nv = a.to_i + b.to_i
|
212
|
+
nv -= @p if nv >= @p
|
213
|
+
nv
|
214
|
+
end
|
215
|
+
|
216
|
+
def sub(a,b)
|
217
|
+
nv = a.to_i - b.to_i
|
218
|
+
nv += @p if nv < 0
|
219
|
+
nv
|
220
|
+
end
|
221
|
+
|
222
|
+
def mul(a,b)
|
223
|
+
# Naive implementation of multiply
|
224
|
+
(a.to_i * b.to_i) % @p
|
225
|
+
end
|
226
|
+
|
227
|
+
def inv(v)
|
228
|
+
#puts "Inversion"
|
229
|
+
return self.class.eea(@p, v.to_i)[1]
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
|
234
|
+
##
|
235
|
+
# rv = (a / b)
|
236
|
+
#
|
237
|
+
def div(a,b)
|
238
|
+
a.to_i * inv(b.to_i)
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
##
|
243
|
+
# rv = b^e
|
244
|
+
#
|
245
|
+
def exp(b,e)
|
246
|
+
self.class.rosetta_mod_exp(b.to_i, e.to_i, @p)
|
247
|
+
end
|
248
|
+
|
249
|
+
def sqrt(n)
|
250
|
+
n = n.to_i
|
251
|
+
return nil if exp(n, (@p-1)/2) != 1
|
252
|
+
|
253
|
+
if (@p % 4) == 3
|
254
|
+
r = exp(n, (p+1) / 4)
|
255
|
+
return [ r, -r % @p ]
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
|
260
|
+
##
|
261
|
+
## Implement Tonelli-Shanks (from https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm )
|
262
|
+
##
|
263
|
+
|
264
|
+
# Factor out Q and S
|
265
|
+
#
|
266
|
+
#
|
267
|
+
p1 = @p-1
|
268
|
+
|
269
|
+
q,s = nil,nil
|
270
|
+
|
271
|
+
p1.bit_length.times.each do |_s|
|
272
|
+
|
273
|
+
_q, _res = p1.ferm_ndiv(1 << _s)
|
274
|
+
## puts [_s, _q, _res].inspect
|
275
|
+
|
276
|
+
if _res == 0 and _q.odd?
|
277
|
+
q,s = _q, _s
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
## puts "q,s: #{[q,s].inspect}"
|
282
|
+
|
283
|
+
# Find `z` such that Legendre ( z | p ) == -1
|
284
|
+
z = nil
|
285
|
+
(1..@p).each{|_z| (z = _z; break) if self.exp(_z, (@p-1)/2) > 1 }
|
286
|
+
## puts "Found z: #{z}"
|
287
|
+
|
288
|
+
c = exp(z, q)
|
289
|
+
## puts "Calculated c: #{c}"
|
290
|
+
|
291
|
+
r = nil
|
292
|
+
_r = exp(n, (q+1) / 2 )
|
293
|
+
t = exp(n, q)
|
294
|
+
m = s
|
295
|
+
|
296
|
+
|
297
|
+
@p.times do
|
298
|
+
|
299
|
+
if (t == 1)
|
300
|
+
r = _r
|
301
|
+
## puts "R is #{r}"
|
302
|
+
break
|
303
|
+
end
|
304
|
+
|
305
|
+
# Modify t and R
|
306
|
+
#
|
307
|
+
|
308
|
+
# find i such that 0 < i < M, such that t**2**i == 1 (mod p)
|
309
|
+
i = nil
|
310
|
+
i = (1..(m-1)).find{|_i| exp(t, (1<<_i)) == 1 }
|
311
|
+
## puts "Found i: #{i}"
|
312
|
+
|
313
|
+
b = exp(c, (1 << (m - i - 1)))
|
314
|
+
_r = mul(_r, b)
|
315
|
+
t = mul(t, exp(b, 2) )
|
316
|
+
c = exp(b, 2)
|
317
|
+
|
318
|
+
m = i
|
319
|
+
|
320
|
+
## puts({:b => b, :r => _r, :t => t, :c => c}.inspect)
|
321
|
+
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
[r, @p - r] if r
|
326
|
+
end
|
327
|
+
|
328
|
+
|
329
|
+
|
330
|
+
class EC
|
331
|
+
attr_reader :field
|
332
|
+
def initialize(field, coeffs=nil)
|
333
|
+
@coeffs = coeffs
|
334
|
+
@field = field
|
335
|
+
end
|
336
|
+
|
337
|
+
def on_curve(x,y)
|
338
|
+
raise NotImplementedError.new
|
339
|
+
end
|
340
|
+
|
341
|
+
def naive_points
|
342
|
+
points = [ECInfinity]
|
343
|
+
@field.p.times do |x|
|
344
|
+
@field.p.times do |y|
|
345
|
+
points << [x,y] if on_curve(x,y)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
points
|
349
|
+
end
|
350
|
+
|
351
|
+
def point_add(point_a, point_b)
|
352
|
+
xa = point_a[0].kind_of?(FFieldValue) ? point_a[0] : @field[point_a[0]]
|
353
|
+
xb = point_b[0].kind_of?(FFieldValue) ? point_b[0] : @field[point_b[0]]
|
354
|
+
|
355
|
+
ya = point_a[1].kind_of?(FFieldValue) ? point_a[1] : @field[point_a[1]]
|
356
|
+
yb = point_b[1].kind_of?(FFieldValue) ? point_b[1] : @field[point_b[1]]
|
357
|
+
|
358
|
+
if xa == xb and ya == yb
|
359
|
+
return double_point(point_a)
|
360
|
+
end
|
361
|
+
#puts "point_add: #{point_a.inspect} + #{point_b.inspect}"
|
362
|
+
|
363
|
+
# All the following operations are in F_p (eg, "mod p")
|
364
|
+
|
365
|
+
s = (yb - ya) / (xb - xa)
|
366
|
+
#puts "Slope: #{s}"
|
367
|
+
|
368
|
+
xc = s**2 - xa - xb
|
369
|
+
yc = (ya * -1) + (xa - xc) * s
|
370
|
+
|
371
|
+
[xc, yc]
|
372
|
+
end
|
373
|
+
|
374
|
+
def scale_naive(k, point_a)
|
375
|
+
point = point_a
|
376
|
+
|
377
|
+
(k-1).times do
|
378
|
+
point = point_add(point, point_a)
|
379
|
+
end
|
380
|
+
|
381
|
+
point
|
382
|
+
end
|
383
|
+
|
384
|
+
def scale_double_add(k, point_a)
|
385
|
+
t = point_a
|
386
|
+
|
387
|
+
bits = k.bit_length
|
388
|
+
|
389
|
+
(bits-1).times.to_a.reverse.each do |bit|
|
390
|
+
t = point_add( t, t )
|
391
|
+
if (k >> bit) & 0x1 == 1
|
392
|
+
t = point_add(t, point_a)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
t
|
396
|
+
end
|
397
|
+
|
398
|
+
end
|
399
|
+
|
400
|
+
|
401
|
+
##
|
402
|
+
#
|
403
|
+
# Toy Montgomery curve to test
|
404
|
+
#
|
405
|
+
# Montgomery curves have the form:
|
406
|
+
#
|
407
|
+
# B * y^2 = x^3 + A * x^2 + x
|
408
|
+
#
|
409
|
+
#
|
410
|
+
#
|
411
|
+
# Test cases:
|
412
|
+
#
|
413
|
+
# E(@a = 3, @b = 1) over p=47
|
414
|
+
#
|
415
|
+
# H = [5,8] on curve.
|
416
|
+
#
|
417
|
+
#
|
418
|
+
class MontgomeryEC < EC
|
419
|
+
attr_accessor :a, :b
|
420
|
+
|
421
|
+
def initialize(field, a: nil, b:nil)
|
422
|
+
super(field)
|
423
|
+
|
424
|
+
@a = @field[ a || 3]
|
425
|
+
@b = @field[ b || 1]
|
426
|
+
|
427
|
+
@a24 = (@a + 2) / @field[4]
|
428
|
+
end
|
429
|
+
|
430
|
+
|
431
|
+
def on_curve(x,y)
|
432
|
+
x = @field[x] unless x.kind_of? FFieldValue
|
433
|
+
y = @field[y] unless y.kind_of? FFieldValue
|
434
|
+
|
435
|
+
(@b * y**2) == (x**3) + x**2 * @a + x
|
436
|
+
end
|
437
|
+
|
438
|
+
|
439
|
+
##
|
440
|
+
#
|
441
|
+
# Add points in affine coordinates
|
442
|
+
#
|
443
|
+
def point_add(point_a, point_b)
|
444
|
+
|
445
|
+
return point_b if point_a == ECInfinity
|
446
|
+
return point_a if point_b == ECInfinity
|
447
|
+
|
448
|
+
xa = point_a[0].kind_of?(FFieldValue) ? point_a[0] : @field[point_a[0]]
|
449
|
+
xb = point_b[0].kind_of?(FFieldValue) ? point_b[0] : @field[point_b[0]]
|
450
|
+
|
451
|
+
ya = point_a[1].kind_of?(FFieldValue) ? point_a[1] : @field[point_a[1]]
|
452
|
+
yb = point_b[1].kind_of?(FFieldValue) ? point_b[1] : @field[point_b[1]]
|
453
|
+
|
454
|
+
#puts "MontgomeryEC#point-add: #{[xa, ya].inspect} + #{[xb, yb].inspect}"
|
455
|
+
|
456
|
+
if xa == xb and ya == -yb
|
457
|
+
return ECInfinity
|
458
|
+
end
|
459
|
+
|
460
|
+
if xa == xb and ya == yb
|
461
|
+
return double_point(point_a)
|
462
|
+
end
|
463
|
+
|
464
|
+
# All the following operations are in F_p (eg, "mod p")
|
465
|
+
|
466
|
+
l = ( yb - ya) / (xb - xa)
|
467
|
+
m = ya - l * xa
|
468
|
+
|
469
|
+
xc = @b * l**2 - @a - xa - xb
|
470
|
+
yc = (xa * 2 + xb + @a) * (yb - ya) / (xb - xa) - ( @b * (yb - ya) ** 3 ) / (xb - xa)**3 - ya
|
471
|
+
[xc, yc]
|
472
|
+
end
|
473
|
+
|
474
|
+
|
475
|
+
##
|
476
|
+
#
|
477
|
+
# Doubles a point in affine coordinates
|
478
|
+
#
|
479
|
+
def double_point(point_a)
|
480
|
+
#puts "Double point: #{point_a.inspect}"
|
481
|
+
|
482
|
+
return point_a if point_a == ECInfinity
|
483
|
+
|
484
|
+
xa = point_a[0].kind_of?(FFieldValue) ? point_a[0] : @field[point_a[0]]
|
485
|
+
ya = point_a[1].kind_of?(FFieldValue) ? point_a[1] : @field[point_a[1]]
|
486
|
+
|
487
|
+
|
488
|
+
bb_inv = (@b * 2 * ya).inv
|
489
|
+
|
490
|
+
c1 = (xa**2 * 3 + @a * xa * 2 + 1 )
|
491
|
+
|
492
|
+
|
493
|
+
xc = @b * c1**2 * bb_inv**2 - @a - xa - xa
|
494
|
+
yc = (xa * 2 + xa + @a) * c1 / (@b * ya * 2) - @b * c1**3 * bb_inv**3 - ya
|
495
|
+
|
496
|
+
|
497
|
+
#x3 = b* (3*x12+2*a*x1+1) **2 / (2*b*y1)**2 -a-x1-x1
|
498
|
+
|
499
|
+
#y3 = (2*x1+x1+a) *(3*x12+2*a*x1+1)/(2*b*y1)-b*(3*x12+2*a*x1+1)3/(2*b*y1)3-y1
|
500
|
+
|
501
|
+
[xc, yc]
|
502
|
+
end
|
503
|
+
|
504
|
+
end
|
505
|
+
|
506
|
+
|
507
|
+
|
508
|
+
class EC25519 < MontgomeryEC
|
509
|
+
attr_accessor :gen
|
510
|
+
|
511
|
+
def initialize
|
512
|
+
super(FField.new(2**255 - 19), a: 486662, b: 1 )
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
end
|
517
|
+
|
518
|
+
|
519
|
+
|
520
|
+
|
521
|
+
|
522
|
+
##
|
523
|
+
|
524
|
+
# Extend the Montgomery EC with projective (XZ) coordinate functions
|
525
|
+
#
|
526
|
+
class FField::MontgomeryEC
|
527
|
+
|
528
|
+
def xz_from_xy(point)
|
529
|
+
return [ @field[point[0].to_i], @field[1] ]
|
530
|
+
end
|
531
|
+
|
532
|
+
|
533
|
+
##
|
534
|
+
# Convert XZ to XY coordinates.
|
535
|
+
#
|
536
|
+
# point must be Array<FastFrac<FFieldValue>> to work in the EC field.
|
537
|
+
#
|
538
|
+
#
|
539
|
+
# Montgomery curves have the form:
|
540
|
+
#
|
541
|
+
# B * y^2 = x^3 + A * x^2 + x
|
542
|
+
#
|
543
|
+
def xz_to_xy(point)
|
544
|
+
|
545
|
+
x = point[0] / point[1]
|
546
|
+
|
547
|
+
y_sq = (x**3 + x**2 * @a + x) / @b
|
548
|
+
|
549
|
+
return y_sq.sqrt.map{|e| [x, e]}
|
550
|
+
end
|
551
|
+
|
552
|
+
|
553
|
+
##
|
554
|
+
#
|
555
|
+
# Implementing "mdbl-1987-m" described in DJB's EFD database:
|
556
|
+
# http://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html
|
557
|
+
#
|
558
|
+
def xz_double(pa)
|
559
|
+
x1 = pa[0]
|
560
|
+
z1 = pa[1]
|
561
|
+
|
562
|
+
c = (x1 - z1)**2
|
563
|
+
d = (x1 * 4 * z1)
|
564
|
+
|
565
|
+
x3 = (x1 + z1) ** 2 * c
|
566
|
+
z3 = d*( c + @a24 * d )
|
567
|
+
[x3, z3]
|
568
|
+
end
|
569
|
+
|
570
|
+
###
|
571
|
+
### XZ scaling:
|
572
|
+
#
|
573
|
+
# Check out: Montgomery Scalar Multiplication for Genus 2 Curves
|
574
|
+
# p. 3 (Prop 1) + explanation of the ladder in Efficient Elliptic Curve Point Multiplication
|
575
|
+
#
|
576
|
+
|
577
|
+
##
|
578
|
+
#
|
579
|
+
# Implement the XZ coordinates from
|
580
|
+
# "Speeding the Pollard and elliptic curve methods of factorization" p.261
|
581
|
+
#
|
582
|
+
# Actually, best description is at:
|
583
|
+
#
|
584
|
+
# Cryptographic Algorithms on Reconfigurable Hardware p.301
|
585
|
+
#
|
586
|
+
# Actually-- I'm not sure where this one came from. Figuring out XZ
|
587
|
+
# projective point adding was a real pain in the ass!
|
588
|
+
#
|
589
|
+
#
|
590
|
+
# Test points for scaling/point addition and testing the Montgomery ladder.
|
591
|
+
#
|
592
|
+
#(5 : 8 : 1)
|
593
|
+
# --
|
594
|
+
# (2, (14 : 44 : 1))
|
595
|
+
# (3, (41 : 36 : 1))
|
596
|
+
# (4, (34 : 6 : 1))
|
597
|
+
# (5, (23 : 37 : 1))
|
598
|
+
# (6, (17 : 4 : 1))
|
599
|
+
# (7, (43 : 36 : 1))
|
600
|
+
# (8, (8 : 17 : 1))
|
601
|
+
# (9, (40 : 28 : 1))
|
602
|
+
# (10, (7 : 11 : 1))
|
603
|
+
# (11, (46 : 1 : 1))
|
604
|
+
# (12, (27 : 29 : 1))
|
605
|
+
# (13, (20 : 14 : 1))
|
606
|
+
# (14, (6 : 1 : 1))
|
607
|
+
# (15, (35 : 14 : 1))
|
608
|
+
# (16, (36 : 14 : 1))
|
609
|
+
# (17, (45 : 7 : 1))
|
610
|
+
# (18, (18 : 17 : 1))
|
611
|
+
# (19, (39 : 1 : 1))
|
612
|
+
# (20, (37 : 29 : 1))
|
613
|
+
# (41, (41 : 11 : 1))
|
614
|
+
# (42, (14 : 3 : 1))
|
615
|
+
# (43, (5 : 39 : 1))
|
616
|
+
# (44, (0 : 1 : 0))
|
617
|
+
# (45, (5 : 8 : 1))
|
618
|
+
#
|
619
|
+
def xz_simple_add(pa, pb, x)
|
620
|
+
|
621
|
+
return pb if (pa == ECInfinity)
|
622
|
+
return pa if (pb == ECInfinity)
|
623
|
+
|
624
|
+
x1 = pa[0]
|
625
|
+
z1 = pa[1]
|
626
|
+
x2 = pb[0]
|
627
|
+
z2 = pb[1]
|
628
|
+
|
629
|
+
x3 = ( (x1 - z1)*(x2 + z2) + (x1 + z1)*(x2 - z2) )**2
|
630
|
+
z3 = x * ( (x1 - z1)*(x2 + z2) - (x1 + z1)*(x2 - z2) )**2
|
631
|
+
|
632
|
+
[x3, z3]
|
633
|
+
end
|
634
|
+
|
635
|
+
|
636
|
+
def scale_proj(k, p)
|
637
|
+
# puts "Scaling #{k} times: #{p.inspect}"
|
638
|
+
|
639
|
+
pa = ECInfinity
|
640
|
+
pb = p.to_xz
|
641
|
+
|
642
|
+
x = p[0]
|
643
|
+
|
644
|
+
|
645
|
+
bits = k.bit_length
|
646
|
+
# puts "Bits: #{bits}"
|
647
|
+
|
648
|
+
(1..bits).each do |j|
|
649
|
+
# puts "Aff[a:x] = #{[pa, pa.to_xy ]}"
|
650
|
+
# puts "Aff[b:x] = #{[pb, pb.to_xy ]}"
|
651
|
+
|
652
|
+
if (k >> (bits - j) ) & 1 == 0
|
653
|
+
|
654
|
+
# puts "[[ bit: 0 ]]; pb = pa + pb; pa = 2*pa"
|
655
|
+
|
656
|
+
pb = xz_simple_add( pa, pb, x )
|
657
|
+
pa = xz_double( pa )
|
658
|
+
else
|
659
|
+
|
660
|
+
# puts "[[ bit: 1 ]]; pb = 2*pb; pa = pa + pb"
|
661
|
+
|
662
|
+
pa = xz_simple_add( pa, pb, x )
|
663
|
+
pb = xz_double(pb)
|
664
|
+
end
|
665
|
+
|
666
|
+
# puts
|
667
|
+
|
668
|
+
end
|
669
|
+
|
670
|
+
# puts "--end--"
|
671
|
+
# puts "Aff[a:x] = #{pa[0] / pa[1]}"
|
672
|
+
# puts "Aff[b:x] = #{pb[0] / pb[1]}"
|
673
|
+
|
674
|
+
return ECInfinity if pa[1] == 0
|
675
|
+
pa
|
676
|
+
end
|
677
|
+
|
678
|
+
|
679
|
+
##
|
680
|
+
#
|
681
|
+
# List of scaled points of [5,8] on toy curve to test laddering and other
|
682
|
+
# REPL-style exploration/testing to get this working right.
|
683
|
+
#
|
684
|
+
def pts
|
685
|
+
[
|
686
|
+
[ @field[ 0], @field[ 0] ], # 0
|
687
|
+
[ @field[ 5], @field[ 8] ], # 1
|
688
|
+
[ @field[14], @field[44] ], # 2
|
689
|
+
[ @field[41], @field[36] ], # 3
|
690
|
+
[ @field[34], @field[ 6] ], # 4
|
691
|
+
[ @field[23], @field[37] ], # 5
|
692
|
+
[ @field[17], @field[ 4] ], # 6
|
693
|
+
[ @field[43], @field[36] ], # 7
|
694
|
+
[ @field[ 8], @field[17] ], # 8
|
695
|
+
[ @field[40], @field[28] ], # 9
|
696
|
+
]
|
697
|
+
end
|
698
|
+
|
699
|
+
end
|
700
|
+
|
701
|
+
|
702
|
+
|
703
|
+
|
704
|
+
|
705
|
+
|
706
|
+
|
707
|
+
# Module Methods
|
708
|
+
|
709
|
+
CURVE = FField::EC25519.new
|
710
|
+
BASE_XZ = [ CURVE.field[9], CURVE.field[1] ]
|
711
|
+
|
712
|
+
|
713
|
+
|
714
|
+
def self.string_to_number(val)
|
715
|
+
v = 0
|
716
|
+
val.reverse.each_byte do |byte|
|
717
|
+
v = (v << 8) | byte
|
718
|
+
end
|
719
|
+
v
|
720
|
+
end
|
721
|
+
|
722
|
+
def self.number_to_string(v)
|
723
|
+
ary = []
|
724
|
+
while v > 0
|
725
|
+
ary << (v & 0xFF)
|
726
|
+
v >>= 8
|
727
|
+
end
|
728
|
+
ary.pack('c*')
|
729
|
+
end
|
730
|
+
|
731
|
+
def self.clamp_string(str)
|
732
|
+
bytes = str.each_byte.to_a
|
733
|
+
bytes[0] &= 248;
|
734
|
+
bytes[31] &= 127;
|
735
|
+
bytes[31] |= 64;
|
736
|
+
|
737
|
+
return bytes.pack('c*')
|
738
|
+
end
|
739
|
+
|
740
|
+
def self.random_secret_str
|
741
|
+
rv = SecureRandom.random_bytes(32)
|
742
|
+
rv = clamp_string(rv)
|
743
|
+
rv
|
744
|
+
end
|
745
|
+
|
746
|
+
def self.random_secret_num
|
747
|
+
string_to_number(random_secret_str)
|
748
|
+
end
|
749
|
+
|
750
|
+
|
751
|
+
#
|
752
|
+
#
|
753
|
+
def self.public_key_num(secret)
|
754
|
+
if String === secret
|
755
|
+
secret = string_to_number(secret)
|
756
|
+
end
|
757
|
+
|
758
|
+
xz = CURVE.scale_proj( secret, BASE_XZ )
|
759
|
+
(xz[0] / xz[1]).to_i
|
760
|
+
end
|
761
|
+
|
762
|
+
|
763
|
+
def self.public_key_str(secret)
|
764
|
+
number_to_string( public_key_num(secret) )
|
765
|
+
end
|
766
|
+
|
767
|
+
##
|
768
|
+
# Secret is a 'k' in Q = k*P
|
769
|
+
#
|
770
|
+
# We want to calculate:
|
771
|
+
#
|
772
|
+
# P_shared_secret = (skey + other_skey) * P_base
|
773
|
+
#
|
774
|
+
# We get there because:
|
775
|
+
#
|
776
|
+
# P_other_pkey = (other_skey) * P_base
|
777
|
+
#
|
778
|
+
#
|
779
|
+
# So continuing to scale P_other_pkey by `skey` will get us to
|
780
|
+
# P_shared_secret. The other party is also doing this calculation; the
|
781
|
+
# Abelian group property means this operation is commutative.
|
782
|
+
#
|
783
|
+
# Note that the Points are all X points in XZ projective space.
|
784
|
+
#
|
785
|
+
#
|
786
|
+
def self.shared_secret_num(pkey, skey)
|
787
|
+
if String === pkey
|
788
|
+
pkey = string_to_number(pkey)
|
789
|
+
end
|
790
|
+
if String === skey
|
791
|
+
skey = string_to_number(skey)
|
792
|
+
end
|
793
|
+
|
794
|
+
shared_xz = CURVE.scale_proj( skey, [ CURVE.field[pkey], CURVE.field[1] ] )
|
795
|
+
|
796
|
+
(shared_xz[0] / shared_xz[1]).to_i # Final projective -> affine inversion
|
797
|
+
end
|
798
|
+
|
799
|
+
def self.shared_secret_str(pkey, skey)
|
800
|
+
number_to_string( shared_secret_num(pkey, skey) )
|
801
|
+
end
|
802
|
+
|
803
|
+
end
|
metadata
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rb-pure25519
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Pruessner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A pure-Ruby EC25519 library for ECDHE
|
14
|
+
email: daniel.pruessner@ieee.org
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- LICENSE
|
20
|
+
- lib/rb-pure25519.rb
|
21
|
+
homepage: http://rubygems.org/gems/rb-pure25519
|
22
|
+
licenses:
|
23
|
+
- MIT
|
24
|
+
metadata: {}
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
requirements: []
|
40
|
+
rubyforge_project:
|
41
|
+
rubygems_version: 2.4.5
|
42
|
+
signing_key:
|
43
|
+
specification_version: 4
|
44
|
+
summary: Ruby Pure EC25519
|
45
|
+
test_files: []
|